Zoeken…


Invoering

Kotlin biedt vele uitbreidingsmethoden voor collecties en iterables voor het toepassen van functionele bewerkingen. Een specifiek Sequence maakt een luie compositie van verschillende van dergelijke bewerkingen mogelijk.

Opmerkingen

Over luiheid

Als u een ketting op een luie manier wilt verwerken, kunt u deze converteren naar een Sequence met behulp van asSequence() vóór de ketting. Aan het einde van de reeks functies krijg je meestal ook een Sequence . Vervolgens kunt u toList() , toSet() , toMap() of een andere functie gebruiken om de Sequence aan het einde te materialiseren.

// switch to and from lazy
val someList = items.asSequence().filter { ... }.take(10).map { ... }.toList()

// switch to lazy, but sorted() brings us out again at the end
val someList = items.asSequence().filter { ... }.take(10).map { ... }.sorted()

Waarom zijn er geen typen?!?

U zult merken dat de Kotlin-voorbeelden de typen niet specificeren. Dit komt omdat Kotlin volledige type inferentie heeft en volledig type veilig is tijdens het compileren. Meer nog dan Java omdat het ook nulbare typen heeft en de gevreesde NPE kan helpen voorkomen. Dus dit in Kotlin:

val someList = people.filter { it.age <= 30 }.map { it.name }

is hetzelfde als:

val someList: List<String> = people.filter { it.age <= 30 }.map { it.name }

Omdat Kotlin weet wat people is, en dat people.age is Int daarom het filter uitdrukking staat slechts vergelijking met een Int en dat people.name is een String dus de map stap produceert een List<String> (alleen-lezen List van String ).

Nu, als people mogelijk null , zoals in een List<People>? vervolgens:

val someList = people?.filter { it.age <= 30 }?.map { it.name }

Retourneert een List<String>? die null-gecontroleerd zou moeten zijn ( of gebruik een van de andere Kotlin-operatoren voor nulbare waarden, zie deze idiomatische manier van Kotlin om met nulbare waarden om te gaan en ook idiomatische manier om nulbare of lege lijst in Kotlin te behandelen )

Streams hergebruiken

In Kotlin hangt het van het type collectie af of deze meerdere keren kan worden geconsumeerd. Een Sequence genereert elke keer een nieuwe iterator, en tenzij het "slechts eenmaal" beweert, kan het telkens naar de start worden gereset wanneer erop wordt gehandeld. Daarom is het volgende mislukt in Java 8-stream, maar werkt in Kotlin:

// Java:
Stream<String> stream =
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> s.startsWith("b"));

stream.anyMatch(s -> true);    // ok
stream.noneMatch(s -> true);   // exception
// Kotlin:  
val stream = listOf("d2", "a2", "b1", "b3", "c").asSequence().filter { it.startsWith('b' ) }

stream.forEach(::println) // b1, b2

println("Any B ${stream.any { it.startsWith('b') }}") // Any B true
println("Any C ${stream.any { it.startsWith('c') }}") // Any C false

stream.forEach(::println) // b1, b2

En in Java om hetzelfde gedrag te krijgen:

// Java:
Supplier<Stream<String>> streamSupplier =
    () -> Stream.of("d2", "a2", "b1", "b3", "c")
          .filter(s -> s.startsWith("a"));

streamSupplier.get().anyMatch(s -> true);   // ok
streamSupplier.get().noneMatch(s -> true);  // ok

Daarom beslist de aanbieder van de gegevens in Kotlin of deze terug kan gaan en een nieuwe iterator kan leveren of niet. Maar als u opzettelijk een Sequence wilt beperken tot eenmalige iteratie, kunt u de functie constrainOnce() voor de Sequence als volgt gebruiken:

val stream = listOf("d2", "a2", "b1", "b3", "c").asSequence().filter { it.startsWith('b' ) }
        .constrainOnce()

stream.forEach(::println) // b1, b2
stream.forEach(::println) // Error:java.lang.IllegalStateException: This sequence can be consumed only once. 

Zie ook:

Verzamel namen in een lijst

// Java:  
List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());
// Kotlin:
val list = people.map { it.name }  // toList() not needed

Converteer elementen naar strings en voeg ze samen, gescheiden door komma's

// Java:
String joined = things.stream()
                       .map(Object::toString)
                       .collect(Collectors.joining(", "));
// Kotlin:
val joined = things.joinToString() // ", " is used as separator, by default

Bereken som van salarissen van werknemer

// Java:
int total = employees.stream()
                      .collect(Collectors.summingInt(Employee::getSalary)));
// Kotlin:
val total = employees.sumBy { it.salary }

Groepsmedewerkers per afdeling

// Java:
Map<Department, List<Employee>> byDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment));
// Kotlin:
val byDept = employees.groupBy { it.department }

Som van lonen per afdeling berekenen

// Java:
Map<Department, Integer> totalByDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment,
                     Collectors.summingInt(Employee::getSalary)));
// Kotlin:
val totalByDept = employees.groupBy { it.dept }.mapValues { it.value.sumBy { it.salary }}

Verdeel studenten in slagen en falen

// Java:
Map<Boolean, List<Student>> passingFailing =
     students.stream()
             .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
// Kotlin:
val passingFailing = students.partition { it.grade >= PASS_THRESHOLD }

Namen van mannelijke leden

// Java:
List<String> namesOfMaleMembersCollect = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .map(p -> p.getName())
    .collect(Collectors.toList());
// Kotlin:
val namesOfMaleMembers = roster.filter { it.gender == Person.Sex.MALE }.map { it.name }

Groepsnamen van leden op basis van geslacht

// Java:
Map<Person.Sex, List<String>> namesByGender =
      roster.stream().collect(
        Collectors.groupingBy(
            Person::getGender,                      
            Collectors.mapping(
                Person::getName,
                Collectors.toList())));
// Kotlin:
val namesByGender = roster.groupBy { it.gender }.mapValues { it.value.map { it.name } }   

Filter een lijst naar een andere lijst

// Java:
List<String> filtered = items.stream()
    .filter( item -> item.startsWith("o") )
    .collect(Collectors.toList());
// Kotlin:
val filtered = items.filter { item.startsWith('o') } 

Een lijst vinden met de kortste reeks

// Java:
String shortest = items.stream()
    .min(Comparator.comparing(item -> item.length()))
    .get();
// Kotlin:
val shortest = items.minBy { it.length }

Verschillende soorten streams # 2 - lui met het eerste item als dit bestaat

// Java:
Stream.of("a1", "a2", "a3")
    .findFirst()
    .ifPresent(System.out::println);    
// Kotlin:
sequenceOf("a1", "a2", "a3").firstOrNull()?.apply(::println)

Verschillende soorten streams # 3 - itereer een reeks gehele getallen

// Java:
IntStream.range(1, 4).forEach(System.out::println);
// Kotlin:  (inclusive range)
(1..3).forEach(::println)

Verschillende soorten streams # 4 - itereer een array, wijs de waarden toe, bereken het gemiddelde

// Java:
Arrays.stream(new int[] {1, 2, 3})
    .map(n -> 2 * n + 1)
    .average()
    .ifPresent(System.out::println); // 5.0    
// Kotlin:
arrayOf(1,2,3).map { 2 * it + 1}.average().apply(::println)

Verschillende soorten streams # 5 - ituleer lui een lijst met strings, wijs de waarden toe, converteer naar Int, vind max

// Java:
Stream.of("a1", "a2", "a3")
    .map(s -> s.substring(1))
    .mapToInt(Integer::parseInt)
    .max()
    .ifPresent(System.out::println);  // 3
// Kotlin:
sequenceOf("a1", "a2", "a3")
    .map { it.substring(1) }
    .map(String::toInt)
    .max().apply(::println)

Verschillende soorten stromen # 6 - itereer lui een stroom Ints, breng de waarden in kaart, druk resultaten af

// Java:
IntStream.range(1, 4)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

// a1
// a2
// a3    
// Kotlin:  (inclusive range)
(1..3).map { "a$it" }.forEach(::println)

Verschillende soorten streams # 7 - dubbel lui itereren, kaart naar Int, kaart naar String, print elk

// Java:
Stream.of(1.0, 2.0, 3.0)
    .mapToInt(Double::intValue)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

// a1
// a2
// a3
// Kotlin:
sequenceOf(1.0, 2.0, 3.0).map(Double::toInt).map { "a$it" }.forEach(::println)

Items in een lijst tellen nadat het filter is toegepast

// Java:
long count = items.stream().filter( item -> item.startsWith("t")).count();
// Kotlin:
val count = items.filter { it.startsWith('t') }.size
// but better to not filter, but count with a predicate
val count = items.count { it.startsWith('t') }

Hoe streams werken - filter, hoofdletters en sorteer vervolgens een lijst

// Java:
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");

myList.stream()
      .filter(s -> s.startsWith("c"))
      .map(String::toUpperCase)
     .sorted()
     .forEach(System.out::println);

// C1
// C2
// Kotlin:
val list = listOf("a1", "a2", "b1", "c2", "c1")
list.filter { it.startsWith('c') }.map (String::toUpperCase).sorted()
        .forEach (::println)

Verschillende soorten streams # 1 - enthousiast gebruikend eerste item als het bestaat

// Java:
Arrays.asList("a1", "a2", "a3")
    .stream()
    .findFirst()
    .ifPresent(System.out::println);    
// Kotlin:
listOf("a1", "a2", "a3").firstOrNull()?.apply(::println)

of maak een uitbreidingsfunctie op String met de naam ifPresent:

// Kotlin:
inline fun String?.ifPresent(thenDo: (String)->Unit) = this?.apply { thenDo(this) }

// now use the new extension function:
listOf("a1", "a2", "a3").firstOrNull().ifPresent(::println)

Zie ook: de functie apply()

Zie ook: Uitbreidingsfuncties

Zie ook: ?. Safe Call-operator en in het algemeen nullabiliteit: http://stackoverflow.com/questions/34498562/in-kotlin-what-is-the-idiomatic-way-to-deal-with-nullable-values-referencing-o/34498563 # 34498563

Verzamel voorbeeld # 5 - vind mensen van wettelijke leeftijd, uitvoer geformatteerde string

// Java:
String phrase = persons
        .stream()
        .filter(p -> p.age >= 18)
        .map(p -> p.name)
        .collect(Collectors.joining(" and ", "In Germany ", " are of legal age."));

System.out.println(phrase);
// In Germany Max and Peter and Pamela are of legal age.    
// Kotlin:
val phrase = persons
        .filter { it.age >= 18 }
        .map { it.name }
        .joinToString(" and ", "In Germany ", " are of legal age.")

println(phrase)
// In Germany Max and Peter and Pamela are of legal age.

En als een kanttekening, in Kotlin we kunnen eenvoudig creëren data klassen en de testgegevens instantiëren als volgt:

// Kotlin:
// data class has equals, hashcode, toString, and copy methods automagically
data class Person(val name: String, val age: Int) 

val persons = listOf(Person("Tod", 5), Person("Max", 33), 
                     Person("Frank", 13), Person("Peter", 80),
                     Person("Pamela", 18))

Verzamel voorbeeld # 6 - groepeer mensen op leeftijd, afdrukleeftijd en namen samen

// Java:
Map<Integer, String> map = persons
        .stream()
        .collect(Collectors.toMap(
                p -> p.age,
                p -> p.name,
                (name1, name2) -> name1 + ";" + name2));

System.out.println(map);
// {18=Max, 23=Peter;Pamela, 12=David}    

Ok, een interessantere zaak hier voor Kotlin. Eerst de verkeerde antwoorden om variaties van het maken van een Map uit een verzameling / reeks te verkennen:

// Kotlin:
val map1 = persons.map { it.age to it.name }.toMap()
println(map1)
// output: {18=Max, 23=Pamela, 12=David} 
// Result: duplicates overridden, no exception similar to Java 8

val map2 = persons.toMap({ it.age }, { it.name })
println(map2)
// output: {18=Max, 23=Pamela, 12=David} 
// Result: same as above, more verbose, duplicates overridden

val map3 = persons.toMapBy { it.age }
println(map3)
// output: {18=Person(name=Max, age=18), 23=Person(name=Pamela, age=23), 12=Person(name=David, age=12)}
// Result: duplicates overridden again

val map4 = persons.groupBy { it.age }
println(map4)
// output: {18=[Person(name=Max, age=18)], 23=[Person(name=Peter, age=23), Person(name=Pamela, age=23)], 12=[Person(name=David, age=12)]}
// Result: closer, but now have a Map<Int, List<Person>> instead of Map<Int, String>

val map5 = persons.groupBy { it.age }.mapValues { it.value.map { it.name } }
println(map5)
// output: {18=[Max], 23=[Peter, Pamela], 12=[David]}
// Result: closer, but now have a Map<Int, List<String>> instead of Map<Int, String>

En nu voor het juiste antwoord:

// Kotlin:
val map6 = persons.groupBy { it.age }.mapValues { it.value.joinToString(";") { it.name } }

println(map6)
// output: {18=Max, 23=Peter;Pamela, 12=David}
// Result: YAY!!

We moesten alleen de overeenkomende waarden samenvoegen om de lijsten samen te vouwen en een transformator te bieden om joinToString te verplaatsen van de instantie Person naar de Person.name .

Verzamel voorbeeld # 7a - Kaartnamen, voeg samen met scheidingsteken

// Java (verbose):
Collector<Person, StringJoiner, String> personNameCollector =
Collector.of(
        () -> new StringJoiner(" | "),          // supplier
        (j, p) -> j.add(p.name.toUpperCase()),  // accumulator
        (j1, j2) -> j1.merge(j2),               // combiner
        StringJoiner::toString);                // finisher

String names = persons
        .stream()
        .collect(personNameCollector);

System.out.println(names);  // MAX | PETER | PAMELA | DAVID    

// Java (concise)
String names = persons.stream().map(p -> p.name.toUpperCase()).collect(Collectors.joining(" | "));
// Kotlin:
val names = persons.map { it.name.toUpperCase() }.joinToString(" | ")

Verzamel voorbeeld # 7b - Verzamel met SummarizingInt

// Java:
IntSummaryStatistics ageSummary =
    persons.stream()
           .collect(Collectors.summarizingInt(p -> p.age));

System.out.println(ageSummary);
// IntSummaryStatistics{count=4, sum=76, min=12, average=19.000000, max=23}    
// Kotlin:

// something to hold the stats...
data class SummaryStatisticsInt(var count: Int = 0,  
                                var sum: Int = 0, 
                                var min: Int = Int.MAX_VALUE, 
                                var max: Int = Int.MIN_VALUE, 
                                var avg: Double = 0.0) {
    fun accumulate(newInt: Int): SummaryStatisticsInt {
        count++
        sum += newInt
        min = min.coerceAtMost(newInt)
        max = max.coerceAtLeast(newInt)
        avg = sum.toDouble() / count
        return this
    }
}

// Now manually doing a fold, since Stream.collect is really just a fold
val stats = persons.fold(SummaryStatisticsInt()) { stats, person -> stats.accumulate(person.age) }

println(stats)
// output: SummaryStatisticsInt(count=4, sum=76, min=12, max=23, avg=19.0)

Maar het is beter om een uitbreidingsfunctie te maken, 2 die eigenlijk overeenkomt met stijlen in Kotlin stdlib:

// Kotlin:
inline fun Collection<Int>.summarizingInt(): SummaryStatisticsInt
        = this.fold(SummaryStatisticsInt()) { stats, num -> stats.accumulate(num) }

inline fun <T: Any> Collection<T>.summarizingInt(transform: (T)->Int): SummaryStatisticsInt =
        this.fold(SummaryStatisticsInt()) { stats, item -> stats.accumulate(transform(item)) }

Nu hebt u twee manieren om de nieuwe functies voor summarizingInt te gebruiken:

val stats2 = persons.map { it.age }.summarizingInt()

// or

val stats3 = persons.summarizingInt { it.age }

En al deze produceren dezelfde resultaten. We kunnen deze extensie ook maken om te werken aan Sequence en voor geschikte primitieve types.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow