Kotlin
Java 8-Stream-Entsprechungen
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:
- API-Referenz für Erweiterungsfunktionen für Iterable
- API-Referenz für Erweiterungsfunktionen für Array
- API-Referenz für Erweiterungsfunktionen für List
- API-Referenz für Erweiterungsfunktionen für Map
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.