← Назад к вопросам

Что такое asSequence?

1.7 Middle🔥 151 комментариев
#Kotlin основы#Производительность и оптимизация

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

asSequence() — ленивые вычисления в Kotlin

asSequence() преобразует коллекцию (List, Set, Map и т.д.) в последовательность (Sequence) с ленивой (lazy) обработкой элементов. Это мощный инструмент для оптимизации производительности при работе с большими коллекциями.

List vs Sequence: режимы обработки

Список (List) — eager (спешная) обработка:

val numbers = listOf(1, 2, 3, 4, 5)

// Каждая операция проходит по ВСЕм элементам
val result = numbers
    .map { it * 2 }      // 5 элементов → [2, 4, 6, 8, 10]
    .filter { it > 5 }   // Проходит по 5 элементам → [6, 8, 10]
    .take(2)             // Берет 2 элемента → [6, 8]

// Всего операций: 5 + 5 + 2 = 12 операций

Последовательность (Sequence) — lazy (ленивая) обработка:

val numbers = listOf(1, 2, 3, 4, 5)

// Обработка происходит только для нужных элементов
val result = numbers.asSequence()
    .map { it * 2 }      // Не вычисляется! (lazy)
    .filter { it > 5 }   // Не вычисляется! (lazy)
    .take(2)             // Вычисляются только 2 нужных элемента
    .toList()            // Только здесь срабатывают все операции

// Фактический порядок вычисления:
// 1: map(1*2)=2 → filter(2>5)=false → skip
// 2: map(2*2)=4 → filter(4>5)=false → skip
// 3: map(3*2)=6 → filter(6>5)=true → take(1)
// 4: map(4*2)=8 → filter(8>5)=true → take(2) → STOP!

// Всего операций: 2 + 2 + 2 = 6 операций (экономия 50%!)

Синтаксис asSequence()

// Способ 1: asSequence() из коллекции
val sequence = listOf(1, 2, 3).asSequence()

// Способ 2: generateSequence() для бесконечной последовательности
val infiniteSequence = generateSequence(0) { it + 1 }

// Способ 3: sequenceOf() для отдельных элементов
val sequence = sequenceOf(1, 2, 3, 4, 5)

// Способ 4: sequence() lambda
val sequence = sequence {
    yield(1)
    yield(2)
    yield(3)
}

Когда asSequence() дает выигрыш

Используй asSequence() когда:

// 1. Много промежуточных операций + take/first
val result = bigList.asSequence()
    .map { complexCalculation(it) }
    .filter { it.isValid() }
    .map { it.transform() }
    .filter { it.passes() }
    .take(1)  // Нужен только один элемент!
    .toList()

// 2. Большая коллекция с операциями фильтрации
val largeData = generateSequence(1) { it + 1 }.take(1_000_000)
val filtered = largeData.asSequence()
    .filter { it % 2 == 0 }
    .map { it / 2 }
    .take(100)
    .toList()

// 3. Цепочка фильтраций
val result = data.asSequence()
    .filter { it.age > 18 }
    .filter { it.status == "active" }
    .filter { it.balance > 0 }
    .take(10)
    .toList()

Не используй asSequence() когда:

// 1. Нужны ВСЕ результаты (нет early termination)
val result = list.asSequence()  // Неэффективно
    .map { it * 2 }
    .toList()  // Создаст всю коллекцию anyway

// 2. Несколько независимых итераций
val seq = list.asSequence()
val sum = seq.sum()        // Первая итерация
val count = seq.count()    // Вторая итерация (неэффективно!)

// Лучше:
val list = list
val sum = list.sum()
val count = list.count()

Практические примеры

Пример 1: Поиск первого валидного пользователя

data class User(val id: Int, val name: String, val email: String)

// Без asSequence: проверяет ВСЕх пользователей
val user = users
    .map { enrichUserData(it) }    // Вызывается для всех
    .filter { it.isValid() }       // Проверяется для всех
    .first()                        // Берет первого

// С asSequence: проверяет до первого валидного
val user = users.asSequence()
    .map { enrichUserData(it) }    // Вызывается только до первого
    .filter { it.isValid() }       // Проверяется только до первого
    .first()                        // Берет первого и СТОПИТ

Пример 2: Обработка файла по строкам

// Ленивое чтение файла
val lines = File("huge_file.txt").readLines().asSequence()

val result = lines
    .map { it.trim() }
    .filter { it.isNotEmpty() }
    .map { parseData(it) }
    .filter { it.isValid() }
    .take(100)  // Читает только нужное
    .toList()

Пример 3: Бесконечная последовательность

// Генерируем числа Фибоначчи
val fibonacci = generateSequence(1 to 1) { (a, b) -> b to (a + b) }
    .map { it.first }

// Берем первые 10 чисел Фибоначчи
val first10 = fibonacci.take(10).toList()
// [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

// Без asSequence пришлось бы создавать конечный список

Производительность: измеренные результаты

import kotlin.system.measureTimeMillis

val hugeList = (1..10_000_000).toList()

// Список (eager)
var time = measureTimeMillis {
    val result = hugeList
        .map { it * 2 }
        .filter { it > 100_000 }
        .take(10)
        .toList()
}
println("List: ${time}ms")  // ~500ms

// Последовательность (lazy)
time = measureTimeMillis {
    val result = hugeList.asSequence()
        .map { it * 2 }
        .filter { it > 100_000 }
        .take(10)
        .toList()
}
println("Sequence: ${time}ms")  // ~10ms (50x быстрее!)

Важные моменты

  • Sequence — одноразовая — после toList() нельзя повторно использовать
  • Ленивость — операции не выполняются до терминального оператора (toList, first, forEach и т.д.)
  • Порядок вычисления — по элементам, не по операциям (как в Stream Java)
  • Для Android — особенно полезна при работе с Room, Retrofit, больших обработках

Практическое правило

Если в цепочке есть take(), first(), any(), find() или другие ранние остановки — используй asSequence(). Это один из лучших способов оптимизировать Kotlin код без потери читаемости.

Что такое asSequence? | PrepBro