Spec-Zone.ru › Kotlin 2

Последовательности

Наряду с коллекциями стандартная библиотека Kotlin содержит ещё один тип — последовательности (Sequence<T>). В отличие от коллекций, последовательности не содержат элементы, а генерируют их во время итерации. Последовательности предлагают те же функции, что и Iterable, но реализуют другой подход к многоэтапной обработке коллекций.

Если обработка Iterable включает несколько этапов, они выполняются немедленно: каждый этап обработки завершается и возвращает результат — промежуточную коллекцию. Следующий этап выполняется над этой коллекцией. В свою очередь, многоэтапная обработка последовательностей по возможности выполняется лениво: фактические вычисления происходят только тогда, когда запрашивается результат всей цепочки обработки.

Порядок выполнения операций также отличается: Sequence последовательно выполняет все этапы обработки для каждого отдельного элемента. В свою очередь, Iterable выполняет каждый этап для всей коллекции, а затем переходит к следующему этапу.

Таким образом, последовательности позволяют избежать создания результатов промежуточных этапов и тем самым повысить производительность всей цепочки обработки коллекции. Однако ленивый характер последовательностей создаёт дополнительные затраты, которые могут быть существенными при обработке небольших коллекций или выполнении простых вычислений. Поэтому следует учитывать как Sequence, так и Iterable и решить, что лучше подходит для вашего случая.

Создание

Из элементов

Чтобы создать последовательность, вызовите функцию sequenceOf(), указав элементы в качестве аргументов.

val numbersSequence = sequenceOf("four", "three", "two", "one")

Из итерируемого объекта

Если у вас уже есть объект Iterable (например, List или Set), вы можете создать из него последовательность, вызвав asSequence().

val numbers = listOf("one", "two", "three", "four")
val numbersSequence = numbers.asSequence()

Из функции

Ещё один способ создать последовательность — построить её с помощью функции, вычисляющей её элементы. Чтобы создать последовательность на основе функции, вызовите generateSequence(), передав эту функцию в качестве аргумента. При необходимости можно указать первый элемент в виде явного значения или результата вызова функции. Генерация последовательности прекращается, когда предоставленная функция возвращает null. Поэтому последовательность в примере ниже бесконечна.


fun main() {
//sampleStart
    val oddNumbers = generateSequence(1) { it + 2 } // `it` is the previous element
    println(oddNumbers.take(5).toList())
    //println(oddNumbers.count())     // error: the sequence is infinite
//sampleEnd
}

Чтобы создать конечную последовательность с помощью generateSequence(), предоставьте функцию, которая возвращает null после последнего необходимого элемента.


fun main() {
//sampleStart
    val oddNumbersLessThan10 = generateSequence(1) { if (it < 8) it + 2 else null }
    println(oddNumbersLessThan10.count())
//sampleEnd
}

Из фрагментов

Наконец, существует функция, позволяющая создавать элементы последовательности по одному или фрагментами произвольного размера, — функция sequence(). Эта функция принимает лямбда-выражение, содержащее вызовы функций yield() и yieldAll(). Они возвращают элемент потребителю последовательности и приостанавливают выполнение sequence() до тех пор, пока потребитель не запросит следующий элемент. yield() принимает один элемент в качестве аргумента; yieldAll() может принимать объект Iterable, Iterator или другую Sequence. Аргумент Sequence функции yieldAll() может быть бесконечным. Однако такой вызов должен быть последним: все последующие вызовы никогда не будут выполнены.


fun main() {
//sampleStart
    val oddNumbers = sequence {
        yield(1)
        yieldAll(listOf(3, 5))
        yieldAll(generateSequence(7) { it + 2 })
    }
    println(oddNumbers.take(5).toList())
//sampleEnd
}

Операции над последовательностями

Операции над последовательностями можно разделить на следующие группы в зависимости от требований к состоянию:

  • Не требующие состояния операции не требуют состояния и обрабатывают каждый элемент независимо, например, map() или filter(). Такие операции могут также требовать небольшого постоянного объёма состояния для обработки элемента, например, take() или drop().

  • Требующие состояния операции требуют значительного объёма состояния, обычно пропорционального количеству элементов в последовательности.

Если операция над последовательностью возвращает другую последовательность, которая создаётся лениво, она называется промежуточной. В противном случае операция является терминальной. Примеры терминальных операций: toList() или sum(). Получить элементы последовательности можно только с помощью терминальных операций.

Последовательности можно перебирать несколько раз, однако некоторые реализации последовательностей допускают только однократный перебор. Об этом отдельно говорится в их документации.

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

Давайте на примере рассмотрим разницу между Iterable и Sequence.

Итерируемый объект

Предположим, у вас есть список слов. Код ниже отбирает слова длиной более трёх символов и выводит длину первых четырёх таких слов.


fun main() {    
//sampleStart
    val words = "The quick brown fox jumps over the lazy dog".split(" ")
    val lengthsList = words.filter { println("filter: $it"); it.length > 3 }
        .map { println("length: ${it.length}"); it.length }
        .take(4)

    println("Lengths of first 4 words longer than 3 chars:")
    println(lengthsList)
//sampleEnd
}

При запуске этого кода вы увидите, что функции filter() и map() выполняются в том же порядке, в котором они указаны в коде. Сначала вы увидите filter: для всех элементов, затем length: для элементов, оставшихся после фильтрации, а после этого — вывод двух последних строк.

Вот как происходит обработка списка:

List processing

Последовательность

Теперь запишем то же самое с помощью последовательностей:


fun main() {
//sampleStart
    val words = "The quick brown fox jumps over the lazy dog".split(" ")
    //convert the List to a Sequence
    val wordsSequence = words.asSequence()

    val lengthsSequence = wordsSequence.filter { println("filter: $it"); it.length > 3 }
        .map { println("length: ${it.length}"); it.length }
        .take(4)

    println("Lengths of first 4 words longer than 3 chars")
    // terminal operation: obtaining the result as a List
    println(lengthsSequence.toList())
//sampleEnd
}

Результат выполнения этого кода показывает, что функции filter() и map() вызываются только при создании списка результатов. Поэтому сначала вы увидите строку текста "Lengths of..", а затем начнётся обработка последовательности. Обратите внимание: для элементов, оставшихся после фильтрации, отображение выполняется до фильтрации следующего элемента. Когда размер результата достигает 4, обработка прекращается, поскольку это максимально возможный размер, который может вернуть take(4).

Обработка последовательности происходит так:

Sequences processing

В этом примере ленивая обработка элементов и остановка после нахождения четырёх элементов сокращают количество операций по сравнению с подходом на основе списка.

12 августа 2026 г.
Операторы сложения и вычитанияДиапазоны и прогрессии

© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/sequences.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API