Как перестать писать Stream.collect в Kotlin и начать жить
Вы когда-нибудь переходили с Java на Kotlin и чувствовали себя как рыба, выброшенная на берег? Особенно когда дело касалось стримов. В Java 8 мы привыкли к цепочкам .stream().filter().map().collect(). А в Kotlin — бац! — и нет Stream.collect в привычном виде. Но не спешите паниковать. На самом деле, в стандартной библиотеке Kotlin есть всё то же самое, только удобнее, лаконичнее и без лишнего шума.
В этой статье мы разберём, какие аналоги Java 8 Stream.collect существуют в Kotlin, и покажем это на реальных примерах. Если вы ищете ответ на вопрос "What Java 8 Stream.collect equivalents are available in the standard Kotlin library?" — вы по адресу. Мы пройдёмся по самым популярным операциям: от простого сбора в список до сложной группировки и объединения строк.
Готовы? Погнали!
1. Самый простой случай: toList() и toSet()
В Java, чтобы собрать результат стрима в список, вы пишете:
List<String> result = list.stream()
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
В Kotlin это превращается в одну строчку без collect:
val result = list.filter { it.length > 3 }
Да, вы не ослышались. filter в Kotlin уже возвращает список. Но если вам нужно явно указать тип коллекции, используйте toList(), toSet() или toMutableList():
val numbers = listOf(1, 2, 3, 4, 5, 5)
val uniqueNumbers: Set<Int> = numbers.toSet()
// Результат: [1, 2, 3, 4, 5]
Вывод: в Kotlin большинство операций с коллекциями (filter, map, flatMap) сразу возвращают список. collect(Collectors.toList()) вам больше не нужен.
2. Группировка: groupingBy вместо Collectors.groupingBy
Группировка — одна из самых частых задач. В Java это выглядит так:
Map<Integer, List<String>> byLength = list.stream()
.collect(Collectors.groupingBy(String::length));
В Kotlin для этого есть элегантная функция groupBy:
val words = listOf("кот", "код", "котёл", "котик")
val byLength = words.groupBy { it.length }
// Результат: {3=[кот, код], 5=[котёл], 4=[котик]}
Но есть нюанс: groupBy сразу возвращает Map<K, List<V>>. А если вам нужно что-то более сложное, например, подсчитать количество элементов в каждой группе? В Java вы бы использовали Collectors.counting():
Map<Integer, Long> countByLength = list.stream()
.collect(Collectors.groupingBy(String::length, Collectors.counting()));
В Kotlin для этого есть groupingBy + eachCount:
val countByLength = words.groupingBy { it.length }.eachCount()
// Результат: {3=2, 5=1, 4=1}
А если нужно собрать не список, а, скажем, множество? Используйте groupBy с трансформацией:
val byLengthSet = words.groupBy(keySelector = { it.length }, valueTransform = { it.uppercase() })
// Результат: {3=[КОТ, КОД], 5=[КОТЁЛ], 4=[КОТИК]}
Совет: если вам нужно что-то среднее между groupBy и groupingBy, используйте groupingBy — он ленивый и позволяет применять разные агрегации (eachCount, fold, reduce).
3. Объединение строк: joinToString() вместо Collectors.joining()
В Java объединение строк через разделитель:
String joined = list.stream()
.collect(Collectors.joining(", "));
В Kotlin это делается ещё проще:
val words = listOf("Kotlin", "Java", "Python")
val joined = words.joinToString(", ")
// Результат: "Kotlin, Java, Python"
Но joinToString гораздо мощнее. Вы можете добавить префикс, суффикс, ограничить количество элементов и даже задать преобразование для каждого элемента:
val result = words.joinToString(
separator = " | ",
prefix = "[",
postfix = "]",
limit = 2,
truncated = "..."
) { it.uppercase() }
// Результат: "[KOTLIN | JAVA | ...]"
Бонус: если вам нужно просто склеить строки без разделителя, используйте joinTo или конкатенацию через reduce (но это уже экзотика).
4. Суммирование, подсчёт, среднее: sumOf, count, average
В Java для подсчёта суммы вы пишете:
int sum = list.stream()
.collect(Collectors.summingInt(Integer::intValue));
В Kotlin есть встроенные функции для чисел:
val numbers = listOf(1, 2, 3, 4, 5)
val sum = numbers.sum() // 15
val count = numbers.count() // 5
val average = numbers.average() // 3.0
А если нужно посчитать сумму по полю объекта? Используйте sumOf:
data class Product(val name: String, val price: Double)
val products = listOf(
Product("Ноутбук", 50000.0),
Product("Мышь", 1500.0)
)
val totalPrice = products.sumOf { it.price }
// Результат: 51500.0
Для более сложной агрегации (например, произведение или кастомная логика) используйте reduce или fold:
val product = numbers.reduce { acc, i -> acc * i }
// Результат: 120 (1*2*3*4*5)
5. Разделение на две коллекции: partition
В Java для разделения списка на две части по условию вы используете partitioningBy:
Map<Boolean, List<Integer>> partitioned = list.stream()
.collect(Collectors.partitioningBy(n -> n > 10));
В Kotlin есть встроенная функция partition, которая возвращает пару списков:
val numbers = listOf(5, 12, 8, 20, 3)
val (greaterThanTen, lessOrEqualTen) = numbers.partition { it > 10 }
// greaterThanTen: [12, 20]
// lessOrEqualTen: [5, 8, 3]
Это гораздо чита