Java Stream.collect в Kotlin: полный гайд с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как перестать писать 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]

Это гораздо чита

Блоги

Рекомендаций по книгам