Avatar ·

Какие аналоги Java 8 Stream.collect существуют в стандартной библиотеке Kotlin?

Всем привет! Я перехожу с Java на Kotlin и столкнулся с проблемой. В Java я постоянно использовал Stream API и метод `collect()` для работы с коллекциями. Например, чтобы сгруппировать список пользователей по городу или собрать в Map, я писал: ```java List users = getUsers(); Map> byCity = users.stream() .collect(Collectors.groupingBy(User::getCity)); ``` Или чтобы найти уникальные имена и отсортировать их: ```java List uniqueSortedNames = users.stream() .map(User::getName) .distinct() .sorted() .collect(Collectors.toList()); ``` В Kotlin я вижу, что есть функции `map`, `filter`, `sorted`, но я не могу найти прямой аналог `collect()`. Я знаю, что в Kotlin коллекции расширены функциями вроде `toList()`, `toSet()`, `groupBy()`, но не уверен, что это полная замена. Подскажите, пожалуйста, какие именно функции в стандартной библиотеке Kotlin (kotlin-stdlib) заменяют `Stream.collect()` с разными коллекторами? Особенно интересуют: 1. Сбор в List, Set, Map 2. Группировка (groupingBy) 3. Разделение на две коллекции по условию (partitioningBy) 4. Сбор в строку (joining) 5. Сбор с суммированием/подсчетом (summingInt, counting) Заранее спасибо за помощь! <<>>

Прямые аналоги Stream.collect в Kotlin

В Kotlin нет метода collect() в том виде, как в Java Stream API, потому что стандартная библиотека Kotlin использует функции-расширения для коллекций, которые работают сразу с последовательностями (Sequence) или коллекциями. Это часто короче и читаемее. Ниже приведены основные соответствия.

1. Сбор в List, Set, Map

Java: .collect(Collectors.toList()) → Kotlin: .toList()

// Java
List<Integer> list = stream.collect(Collectors.toList());

// Kotlin
val list: List<Int> = sequence.toList()

// Для Set — .toSet(), для Map — .toMap() (из пар)
val set: Set<Int> = listOf(1,2,2,3).toSet() // [1,2,3]
val map: Map<Int, String> = listOf(1 to "a", 2 to "b").toMap()

2. Группировка (аналог groupingBy)

Java: .collect(Collectors.groupingBy(Function)) → Kotlin: .groupBy { keySelector }

// Java
Map<String, List<User>> byCity = users.stream()
    .collect(Collectors.groupingBy(User::getCity));

// Kotlin
val byCity: Map<String, List<User>> = users.groupBy { it.city }

Для группировки с преобразованием значений (например, только имена):

val namesByCity: Map<String, List<String>> = users.groupBy(
    keySelector = { it.city },
    valueTransform = { it.name }
)

3. Разделение на две коллекции (partitioningBy)

Java: .collect(Collectors.partitioningBy(predicate)) → Kotlin: .partition { predicate }

// Java
Map<Boolean, List<User>> adults = users.stream()
    .collect(Collectors.partitioningBy(u -> u.getAge() >= 18));

// Kotlin — возвращает Pair (список для true, список для false)
val (adults, minors) = users.partition { it.age >= 18 }
// adults: List<User>, minors: List<User>

4. Сбор в строку (joining)

Java: .collect(Collectors.joining(", ")) → Kotlin: .joinToString(", ") или .joinTo(StringBuilder())

// Java
String csv = users.stream()
    .map(User::getName)
    .collect(Collectors.joining(", "));

// Kotlin
val csv = users.map { it.name }.joinToString(", ")
// Или напрямую:
val csv2 = users.joinToString(", ") { it.name }

// С префиксом и суффиксом:
val csv3 = users.joinToString(", ", "[", "]") { it.name }

5. Суммирование и подсчет (summingInt, counting)

Java: .collect(Collectors.summingInt(...)) → Kotlin: .sumOf { ... }

// Java
int totalAge = users.stream().collect(Collectors.summingInt(User::getAge));

// Kotlin
val totalAge = users.sumOf { it.age } // Int
val totalAgeLong = users.sumOf { it.age.toLong() } // Long

// Подсчет количества (counting) — просто .count()
val adultCount = users.count { it.age >= 18 }

Важные особенности Kotlin

  • Ленивые вычисления: Если нужна ленивость, как в Java Stream, используйте asSequence() перед цепочкой операций. Например: users.asSequence().filter { ... }.map { ... }.toList()
  • Изменяемые коллекции: В Kotlin коллекции делятся на изменяемые (mutable) и неизменяемые (read-only). Функции вроде toList() возвращают неизменяемый список. Если нужен изменяемый — используйте toMutableList().
  • Сложные коллекторы: Для кастомной агрегации используйте .fold() или .reduce(). Например, чтобы собрать в Map с кастомным слиянием значений:
// Аналог Collectors.toMap(keyMapper, valueMapper, mergeFunction)
val map = users.associateBy(
    keySelector = { it.id },
    valueTransform = { it.name }
)
// Если есть дубликаты ключей — используйте .groupBy() или .fold()

В итоге: в 90% случаев вам не нужен collect() — используйте встроенные функции Kotlin. Они короче, читаемее и типизированы.

<<>>

Альтернативный подход: использование Sequence и fold

Если вам нужна максимальная гибкость, как в Java с кастомным коллектором, используйте Sequence и функцию fold().

// Пример: кастомная агрегация в Map со списками уникальных значений
val result = listOf("apple", "banana", "apricot")
    .asSequence()
    .filter { it.startsWith("a") }
    .fold(mutableMapOf<Char, MutableList<String>>()) { acc, fruit ->
        acc.getOrPut(fruit.first()) { mutableListOf() }
            .add(fruit)
        acc
    }

Этот подход полезен, когда нужно выполнить несколько операций одновременно или собрать результат в структуру, для которой нет готовой функции. Однако помните: fold менее читаем, чем специализированные функции, поэтому используйте его только когда groupBy, partition или associate не подходят.

Также обратите внимание на buildList, buildMap и buildSet из Kotlin 1.6+ — они позволяют создавать коллекции с помощью лямбды-построителя:

val result = buildList {
    addAll(users.filter { it.age > 18 })
    if (size < 5) add(specialUser)
}

Это более идиоматичный способ, чем collect(Collectors.toList()) в Java.