Kotlin Iterable и Sequence выглядят точно так же. Почему требуются два типа?

Оба этих интерфейса определяют только один метод

public operator fun iterator(): Iterator<T>

Документация говорит, что Sequence означает ленивый. Но не Iterable ленивый (если не поддерживается Collection)?

Ответ 1

Основное различие заключается в семантике и реализации функций расширения stdlib для Iterable<T> и Sequence<T>.

  • Для Sequence<T> функции расширения выполняются, где это возможно, лениво, подобно промежуточным операциям Java Streams. Например, Sequence<T>.map {... } возвращает другую Sequence<R> и фактически не обрабатывает элементы, пока не будет toList терминальная операция, такая как toList или fold.

    Рассмотрим этот код:

    val seq = sequenceOf(1, 2)
    val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
    print("before sum ")
    val sum = seqMapped.sum() // terminal
    

    Это печатает:

    before sum 1 2
    

    Sequence<T> предназначен для ленивого использования и эффективной конвейеризации, когда вы хотите максимально сократить работу, выполняемую в терминальных операциях, так же, как в Java Streams. Однако лень вносит некоторые накладные расходы, что нежелательно для обычных простых преобразований небольших коллекций и делает их менее производительными.

    В общем, нет хорошего способа определить, когда это необходимо, поэтому в Kotlin лень stdlib делается явным и извлекается в интерфейс Sequence<T> чтобы по умолчанию не использовать его во всех Iterable.

  • Для Iterable<T>, напротив, функции расширения с семантикой промежуточных операций работают с большим удовольствием, сразу же обрабатывают элементы и возвращают другой Iterable. Например, Iterable<T>.map {... } возвращает List<R> с результатами сопоставления в нем.

    Эквивалентный код для Iterable:

    val lst = listOf(1, 2)
    val lstMapped: List<Int> = lst.map { print("$it "); it * it }
    print("before sum ")
    val sum = lstMapped.sum()
    

    Это распечатывает:

    1 2 before sum
    

    Как сказано выше, Iterable<T> по умолчанию не ленив, и это решение хорошо себя зарекомендовало: в большинстве случаев он имеет хорошую локальность ссылок, таким образом, используя преимущества кэша ЦП, прогнозирования, предварительной выборки и т.д., Так что даже многократное копирование коллекция все еще работает достаточно хорошо и работает лучше в простых случаях с небольшими коллекциями.

    Если вам нужно больше контроля над конвейером оценки, существует явное преобразование в ленивую последовательность с функцией Iterable<T>.asSequence().

Ответ 2

Завершение ответа горячей клавиши:

Важно заметить, как Sequence и Iterable итерируют по всем вашим элементам:

Пример последовательности:

        list.asSequence()
            .filter { field ->
                Log.d("Filter", "filter")
                field.value > 0
            }.map {
                Log.d("Map", "Map")
            }.forEach {
                Log.d("Each", "Each")
            }

Зарегистрировать результат:

фильтр - карта - каждая; фильтр - Карта - Каждый

Итерируемый пример:

             list.filter { field ->
                    Log.d("Filter", "filter")
                    field.value > 0
                }.map {
                    Log.d("Map", "Map")
                }.forEach {
                    Log.d("Each", "Each")
                }

фильтр - фильтр - карта - карта - каждый - каждый