Kotlin
Java 8 équivalents de flux
Recherche…
Introduction
Kotlin fournit de nombreuses méthodes d'extension sur les collections et les itérables pour appliquer des opérations de style fonctionnel. Un type de Sequence
dédié permet la composition paresseuse de plusieurs de ces opérations.
Remarques
À propos de la paresse
Si vous voulez traiter une chaîne paresseuse, vous pouvez convertir en une Sequence
utilisant asSequence()
avant la chaîne. À la fin de la chaîne de fonctions, vous vous retrouvez généralement avec une Sequence
. Ensuite, vous pouvez utiliser toList()
, toSet()
, toMap()
ou une autre fonction pour matérialiser la Sequence
à la fin.
// 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()
Pourquoi n'y a-t-il pas de types?
Vous remarquerez que les exemples Kotlin ne spécifient pas les types. Ceci est dû au fait que Kotlin a une inférence de type complète et est complètement de type sécurisé au moment de la compilation. Plus que Java, car il a également des types nullables et peut aider à empêcher le NPE redouté. Donc ceci à Kotlin:
val someList = people.filter { it.age <= 30 }.map { it.name }
est le même que:
val someList: List<String> = people.filter { it.age <= 30 }.map { it.name }
Comme Kotlin sait ce que sont les people
et que people.age
est Int
l'expression de filtre permet uniquement la comparaison avec un Int
, et que people.name
est une String
conséquent, l'étape map
génère une List<String>
(Listonly List
of String
).
Maintenant, si les people
étaient peut-être null
, comme dans une List<People>?
puis:
val someList = people?.filter { it.age <= 30 }?.map { it.name }
Retourne une List<String>?
cela devrait être vérifié par la valeur null ( ou utiliser l'un des autres opérateurs Kotlin pour les valeurs nullables, voir cette méthode idiomatique de Kotlin pour traiter les valeurs nulles et également la manière idiomatique de gérer les listes nullables ou vides dans Kotlin )
Réutiliser les flux
Dans Kotlin, cela dépend du type de collection si elle peut être consommée plus d'une fois. Une Sequence
génère un nouvel itérateur à chaque fois, et à moins qu'elle n'utilise "une seule fois", elle peut être réinitialisée à chaque fois qu'elle est utilisée. Par conséquent, alors que le suivant échoue dans le flux Java 8, mais fonctionne dans 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
Et en Java pour obtenir le même comportement:
// 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
Par conséquent, dans Kotlin, le fournisseur des données décide s'il peut réinitialiser et fournir un nouvel itérateur ou non. Mais si vous souhaitez contraindre intentionnellement une Sequence
à une itération unique, vous pouvez utiliser la fonction constrainOnce()
pour la Sequence
comme suit:
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.
Voir également:
- Référence API pour les fonctions d'extension pour Iterable
- Référence API pour les fonctions d'extension pour Array
- Référence API pour les fonctions d'extension pour List
- Référence API pour les fonctions d'extension à Map
Accumuler des noms dans une liste
// Java:
List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());
// Kotlin:
val list = people.map { it.name } // toList() not needed
Convertir des éléments en chaînes et les concaténer, séparés par des virgules
// Java:
String joined = things.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
// Kotlin:
val joined = things.joinToString() // ", " is used as separator, by default
Calculer la somme des salaires des employés
// Java:
int total = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary)));
// Kotlin:
val total = employees.sumBy { it.salary }
Employés du groupe par département
// Java:
Map<Department, List<Employee>> byDept
= employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
// Kotlin:
val byDept = employees.groupBy { it.department }
Calculer la somme des salaires par département
// 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 }}
Partitionnez les élèves en passant et échouant
// 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 }
Noms de membres masculins
// 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 }
Noms de groupe des membres inscrits par sexe
// 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 } }
Filtrer une liste dans une autre liste
// Java:
List<String> filtered = items.stream()
.filter( item -> item.startsWith("o") )
.collect(Collectors.toList());
// Kotlin:
val filtered = items.filter { item.startsWith('o') }
Trouver la plus courte chaîne d'une liste
// Java:
String shortest = items.stream()
.min(Comparator.comparing(item -> item.length()))
.get();
// Kotlin:
val shortest = items.minBy { it.length }
Différents types de flux # 2 - utiliser paresseusement le premier élément s'il existe
// Java:
Stream.of("a1", "a2", "a3")
.findFirst()
.ifPresent(System.out::println);
// Kotlin:
sequenceOf("a1", "a2", "a3").firstOrNull()?.apply(::println)
Différents types de flux # 3 - itération d'une gamme d'entiers
// Java:
IntStream.range(1, 4).forEach(System.out::println);
// Kotlin: (inclusive range)
(1..3).forEach(::println)
Différents types de flux # 4 - itérer un tableau, mapper les valeurs, calculer la moyenne
// 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)
Différents types de flux # 5 - répétez lentement une liste de chaînes, mappez les valeurs, convertissez-les en Int, recherchez 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)
Différents types de flux # 6 - itération lente d'un flux d'Ints, mappage des valeurs, impression des résultats
// 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)
Différents types de flux # 7 - itération paresseuse Doubles, mapper à Int, mapper à String, imprimer chaque
// 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)
Comptage d'éléments dans une liste après application du filtre
// 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') }
Comment les flux fonctionnent - filtrer, majuscule, puis trier une 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)
Différents types de flux # 1 - désireux d'utiliser le premier élément s'il existe
// Java:
Arrays.asList("a1", "a2", "a3")
.stream()
.findFirst()
.ifPresent(System.out::println);
// Kotlin:
listOf("a1", "a2", "a3").firstOrNull()?.apply(::println)
ou, créez une fonction d'extension sur String appelée 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)
Voir aussi: fonction apply()
Voir aussi: Fonctions d'extension
Voir aussi: ?.
Opérateur d'appel sécurisé , et en nullité générale: http://stackoverflow.com/questions/34498562/in-kotlin-what-is-the-idiomatic-way-to-deal-with-nullable-values-referencing-o/34498563 # 34498563
Recueillir l'exemple n ° 5 - trouver des personnes d'âge légal, sortir des chaînes formatées
// 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.
Et comme remarque secondaire, dans Kotlin, nous pouvons créer des classes de données simples et instancier les données de test comme suit:
// 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))
Recueillir l'exemple n ° 6 - regrouper les personnes par âge, imprimer l'âge et les noms ensemble
// 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, un cas plus intéressant ici pour Kotlin. D'abord, les mauvaises réponses pour explorer les variantes de création d'une Map
partir d'une collection / séquence:
// 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>
Et maintenant, pour la bonne réponse:
// 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!!
Nous devions simplement joindre les valeurs correspondantes pour joinToString
les listes et fournir un transformateur à joinToString
pour passer d'une instance Person
au nom Person.name
.
Recueillir l'exemple n ° 7a - Noms des cartes, joindre avec un délimiteur
// 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(" | ")
Recueillir l'exemple n ° 7b - Recueillir avec 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)
Mais il est préférable de créer une fonction d’extension 2 pour faire correspondre les styles de 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)) }
Maintenant, vous avez deux façons d'utiliser les nouvelles fonctions de summarizingInt
:
val stats2 = persons.map { it.age }.summarizingInt()
// or
val stats3 = persons.summarizingInt { it.age }
Et tout cela produit les mêmes résultats. Nous pouvons également créer cette extension pour travailler sur la Sequence
et pour les types primitifs appropriés.