Какие аналоги Java 8 Stream.collect существуют в стандартной библиотеке Kotlin?
Привет! Я перехожу с Java на Kotlin и активно изучаю стандартную библиотеку. В Java я часто использовал Stream API, особенно метод collect с различными коллекторами: Collectors.toList(), Collectors.groupingBy(), Collectors.joining() и т.д.
Например, в Java я мог написать:
List<String> names = people.stream()
.filter(p -> p.getAge() > 18)
.map(Person::getName)
.collect(Collectors.toList());
Или сгруппировать людей по возрасту:
Map<Integer, List<Person>> byAge = people.stream()
.collect(Collectors.groupingBy(Person::getAge));
В Kotlin я вижу, что есть функции .filter, .map, но не могу найти прямой аналог .collect(Collectors.toList()). Пытался писать .toList(), но не уверен, что это правильно и эффективно.
Подскажите, какие функции в стандартной библиотеке Kotlin заменяют Stream.collect? Особенно интересуют:
- Сбор в список, множество или другую коллекцию
- Группировка элементов
- Разделение на две коллекции по условию
- Объединение строк
- Подсчёт и суммирование
Заранее спасибо!
<<Полное руководство по замене Java Stream.collect в Kotlin
В Kotlin стандартная библиотека предоставляет гораздо более элегантные и лаконичные аналоги для всех операций Stream.collect. Вам не нужны отдельные коллекторы — всё реализовано через extension-функции для коллекций и последовательностей.
1. Сбор в список, множество или Map
Вместо .collect(Collectors.toList()) используйте просто .toList():
// Java
List<String> names = people.stream()
.filter(p -> p.age > 18)
.map(Person::getName)
.collect(Collectors.toList());
// Kotlin
val names = people
.filter { it.age > 18 }
.map { it.name }
.toList() // или просто .filter{}.map{} — результат уже List
Важно: в Kotlin большинство функций над коллекциями уже возвращают список, поэтому .toList() часто не нужен. Но если вы работаете с Sequence (ленивые вычисления), .toList() — обязательный терминальный оператор.
Для toSet(), toMap() — аналогично:
val uniqueNames = names.toSet()
val nameToAge = people.associate { it.name to it.age } // Map<String, Int>
2. Группировка (groupingBy)
Java-аналог Collectors.groupingBy() — это функция groupBy:
// Java: Map<Integer, List<Person>> byAge = people.stream().collect(Collectors.groupingBy(Person::getAge));
val byAge: Map<Int, List<Person>> = people.groupBy { it.age }
// Более сложный пример: группировка с преобразованием значений
val namesByAge: Map<Int, List<String>> = people.groupBy({ it.age }, { it.name })
Для более сложной агрегации (например, подсчёт элементов в группе) используйте groupingBy + eachCount:
val countByAge: Map<Int, Int> = people.groupingBy { it.age }.eachCount()
3. Разделение на две коллекции (partitioningBy)
Java Collectors.partitioningBy заменяется на partition:
// Java: Map<Boolean, List<Person>> split = people.stream().collect(Collectors.partitioningBy(p -> p.age > 18));
val (adults, minors) = people.partition { it.age >= 18 }
// adults: List<Person>, minors: List<Person>
4. Объединение строк (joining)
Для Collectors.joining() используйте joinToString():
// Java: String joined = people.stream().map(Person::getName).collect(Collectors.joining(", "));
val joined = people.map { it.name }.joinToString(", ")
// Или ещё проще:
val joined = people.joinToString(", ") { it.name }
5. Агрегация: суммирование, среднее, минимум, максимум
Вместо Collectors.summingInt():
// Java: int totalAge = people.stream().collect(Collectors.summingInt(Person::getAge));
val totalAge = people.sumOf { it.age } // Int
val totalAgeDouble = people.sumOf { it.age.toDouble() } // Double
Для среднего:
val averageAge = people.map { it.age }.average() // Double
Минимум/максимум:
val youngest = people.minByOrNull { it.age }
val oldest = people.maxByOrNull { it.age }
6. Сбор в произвольную коллекцию
Если нужно собрать в специфическую коллекцию (например, LinkedList или TreeSet):
val linkedList = listOf(1, 2, 3).toCollection(LinkedList())
val treeSet = listOf(3, 1, 2).toCollection(TreeSet())
7. Сбор с преобразованием (toMap, associate)
Java Collectors.toMap():
// Java: Map<String, Integer> nameToAge = people.stream().collect(Collectors.toMap(Person::getName, Person::getAge));
val nameToAge = people.associate { it.name to it.age }
// Или с обработкой дубликатов:
val nameToAgeSafe = people.associateBy { it.name } // если ключи уникальны
Важное замечание
В Kotlin большинство операций над коллекциями выполняются жадно (eagerly), в отличие от Java Stream, который ленивый. Если вам нужна ленивая обработка (как в Java Stream), используйте Sequence:
val result = people.asSequence()
.filter { it.age > 18 }
.map { it.name }
.toList() // терминальная операция
Таким образом, в Kotlin вам не нужен отдельный класс Collectors — все операции встроены прямо в коллекции и последовательности. Код становится короче и читаемее.
Альтернативный подход: использование mutable-коллекций
Иногда, особенно при работе с большими данными или в циклах, удобнее использовать традиционный императивный подход с изменяемыми коллекциями. В Kotlin это тоже лаконично:
// Вместо сложной цепочки collect
val result = mutableListOf<String>()
for (person in people) {
if (person.age > 18) {
result.add(person.name.uppercase())
}
}
Для группировки с накоплением:
val byAge = mutableMapOf<Int, MutableList<Person>>()
for (person in people) {
byAge.getOrPut(person.age) { mutableListOf() }.add(person)
}
Этот подход даёт больше контроля и часто работает быстрее при очень больших коллекциях, так как избегает создания промежуточных объектов. Однако в 95% случаев функциональный стиль с filter, map и groupBy предпочтительнее из-за читаемости.
Также помните про buildList и buildMap — они позволяют писать императивный код в функциональном стиле:
val names = buildList {
for (person in people) {
if (person.age > 18) add(person.name)
}
}
Выбирайте подход под конкретную задачу — Kotlin даёт вам свободу!
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги
Собеседование по Kotlin: топ вопросов и ответы для прохождения