Suche…


Einführung

Kotlin bietet viele Erweiterungsmethoden für Sammlungen und iterierbare Elemente zum Anwenden funktionaler Operationen. Ein dedizierter Sequence ermöglicht die faule Komposition mehrerer solcher Operationen.

Bemerkungen

Über Faulheit

Wenn Sie eine Kette faul verarbeiten möchten, können Sie sie mit asSequence() vor der Kette in eine Sequence asSequence() . Am Ende der Funktionskette erhalten Sie normalerweise auch eine Sequence . Dann können Sie toList() , toSet() , toMap() oder eine andere Funktion verwenden, um die Sequence am Ende zu materialisieren.

// 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()

Warum gibt es keine Typen?!?

Sie werden feststellen, dass die Kotlin-Beispiele die Typen nicht angeben. Dies liegt daran, dass Kotlin die vollständige Typinferenz hat und zur Kompilierzeit vollständig typsicher ist. Mehr als Java, da es auch nullfähige Typen hat und dazu beitragen kann, die gefürchtete NPE zu verhindern. Also das in Kotlin:

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

ist das gleiche wie:

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

Da Kotlin weiß, was people sind und dass people.age Int ist, people.age der Filterausdruck nur Vergleiche mit einem Int , und da people.name ein String erzeugt der map eine List<String> (Readonly List of String ).

Nun, wenn people möglicherweise null , als List<People>? dann:

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

Gibt eine List<String>? das müsste auf null geprüft werden ( oder verwenden Sie einen der anderen Kotlin-Operatoren für nullfähige Werte, siehe diese idiomatische Methode von Kotlin zum Umgang mit nullfähigen Werten sowie die idiomatische Art, nullfähige oder leere Listen in Kotlin zu behandeln )

Streams wiederverwenden

In Kotlin hängt es von der Art der Sammlung ab, ob sie mehr als einmal konsumiert werden kann. Eine Sequence generiert jedes Mal einen neuen Iterator, und wenn sie nicht "nur einmal verwenden" behauptet, kann sie bei jeder Ausführung auf den Start zurückgesetzt werden. Daher schlägt folgendes in Java 8-Stream fehl, funktioniert aber 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

Und in Java, um das gleiche Verhalten zu erhalten:

// 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

Daher entscheidet der Anbieter der Daten in Kotlin, ob er zurücksetzen und einen neuen Iterator bereitstellen kann oder nicht. Wenn Sie jedoch eine Sequence absichtlich auf eine Iteration beschränken möchten, können Sie die Funktion constrainOnce() für Sequence wie folgt verwenden:

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. 

Siehe auch:

Namen in einer Liste sammeln

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

Konvertieren Sie Elemente in Strings und verketten Sie sie, getrennt durch Kommas

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

Berechnen Sie die Summe der Gehälter des Angestellten

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

Mitarbeiter nach Abteilungen zusammenfassen

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

Berechnen Sie die Summe der Gehälter nach Abteilung

// 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 }}

Trennen Sie die Schüler in Pass und Misserfolg

// 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 der männlichen Mitglieder

// 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 }

Gruppennamen der Mitglieder in der Liste nach Geschlecht

// 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 } }   

Filtern Sie eine Liste in eine andere Liste

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

Suchen Sie nach der kürzesten Zeichenfolge einer Liste

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

Verschiedene Arten von Streams # 2 - faul beim ersten Artikel, falls vorhanden

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

Verschiedene Arten von Streams # 3 - eine Reihe von ganzen Zahlen durchlaufen

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

Verschiedene Arten von Streams # 4 - Durchlaufen Sie ein Array, ordnen Sie die Werte zu und berechnen Sie den Durchschnitt

// 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)

Verschiedene Arten von Streams Nr. 5: Durchlaufen Sie faul eine Liste von Strings, ordnen Sie die Werte zu, konvertieren Sie in Int, finden Sie 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)

Verschiedene Arten von Streams Nr. 6: Durchlaufen Sie einen Ints-Stream träge, ordnen Sie die Werte zu und drucken Sie die Ergebnisse

// 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)

Verschiedene Arten von Streams Nr. 7 - Doppelter Durchlauf durchlässig, Zuordnung zu Int, Zuordnung zu Zeichenfolge, Drucken jeweils

// 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)

Elemente in einer Liste zählen, nachdem der Filter angewendet wurde

// 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') }

Funktionsweise von Streams: Filtern Sie Großbuchstaben und sortieren Sie dann eine Liste

// 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)

Verschiedene Arten von Streams Nr. 1 - eifrig mit dem ersten Element, falls vorhanden

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

oder erstellen Sie eine Erweiterungsfunktion für String mit dem Namen 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)

Siehe auch: apply() Funktion

Siehe auch: Erweiterungsfunktionen

Siehe auch: ?. Safe Call-Operator und im Allgemeinen nullability: http://stackoverflow.com/questions/34498562/in-kotlin-what-is-the-idiomatic-way-to-deal-mit-nullable-values-referencing-o/34498563 # 34498563

Sammeln Sie Beispiel 5 - finden Sie Personen im gesetzlichen Alter, geben Sie eine formatierte Zeichenfolge aus

// 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.

Als Randnotiz können wir in Kotlin einfache Datenklassen erstellen und die Testdaten wie folgt instanziieren:

// 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))

Sammle Beispiel # 6 - gruppiere Leute nach Alter, Alter und Namen zusammen

// 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, ein interessanter Fall hier für Kotlin. Zuerst die falschen Antworten, um Variationen beim Erstellen einer Map aus einer Sammlung / Sequenz zu untersuchen:

// 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>

Und nun zur richtigen Antwort:

// 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!!

Wir mussten nur die übereinstimmenden Werte zusammenfügen, um die Listen zu joinToString , und joinToString einen Transformer joinToString , um von der Person zum Person zu Person.name .

Sammeln Sie Beispiel # 7a - Kartennamen, verbinden Sie sich mit Trennzeichen

// 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(" | ")

Sammeln Sie Beispiel # 7b - Sammeln Sie mit 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)

Es ist jedoch besser, eine Erweiterungsfunktion zu erstellen, 2, die tatsächlich mit den Styles in Kotlin stdlib übereinstimmt:

// 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)) }

Jetzt haben Sie zwei Möglichkeiten , um die neuen verwenden summarizingInt Funktionen:

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

// or

val stats3 = persons.summarizingInt { it.age }

Und alle diese produzieren die gleichen Ergebnisse. Wir können diese Erweiterung auch erstellen, um in Sequence und für entsprechende primitive Typen zu arbeiten.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow