Sequence は、要素を1件ずつ、必要になったときに作って流す「遅延評価」のコレクション処理です。 List の map・filter は段ごとに新しいリストを作りますが、asSequence() を付けると、1件ずつ全部の段を通し、途中のリストを作りません。first() や take() で途中で止めると、残りの要素は処理されません。

val data = listOf(1, 2, 3, 4)

data.map { it * 10 }.filter { it > 15 }.first()                 // List
data.asSequence().map { it * 10 }.filter { it > 15 }.first()    // Sequence

map と filter にログを入れて動かした結果です。

== List                 == Sequence
  map(1)                  map(1)
  map(2)                  filter(10)
  map(3)                  map(2)
  map(4)                  filter(20)
  filter(10)            result=20
  filter(20)
  filter(30)
  filter(40)
result=20

List は「map を全部 → filter を全部」の段ごと、Sequence は「1件目を map → filter、2件目を…」の1件ずつでした。 Sequence は 20 が見つかった時点で止まり、3件目・4件目は処理していません。

測った速さ(中央値)の要約です。詳しい表と測り方は後半にあります。

処理(100万件) List Sequence 結果
map → filter → 最初の10件 33.5 ms 0.0002 ms Sequence が圧倒的に速い
map → filter → sum(全部) 27.5 ms 4.6 ms Sequence が速い
map を5段つなぐ → sum 47.7 ms 15.3 ms Sequence が速い
map だけ → toList 14.8 ms 35.0 ms List が速い

確認環境:Kotlin コンパイラ kotlinc 2.4.20、OpenJDK 21.0.10、Linux(2コアの仮想マシン、メモリ 7GB)で、この記事のコードをコンパイル・実行しました。速さは JMH(Java のベンチマーク用ツール)を使わず、System.nanoTime() で何度も測った中央値です(測り方は後述)。Sequence の仕様は kotlinlang.org のドキュメントで確かめました(確認日 2026年10月13日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Kotlin Sequenceの使い方|Listとの違いと速さを実測」

Sequence の作り方

作り方 例 向いている場面
asSequence() list.asSequence() 今ある List・Set・Range から
sequenceOf() sequenceOf(1, 2, 3) 要素を直接書く
generateSequence() generateSequence(1) { it * 2 } 前の値から次の値を作る
sequence { yield() } sequence { yield(1); yieldAll(list) } 複雑な手順で1件ずつ作る
Iterator.asSequence() iterator.asSequence() Iterator しか無いとき(1回しか回せない)
Stream.asSequence() stream.asSequence() Java の Stream から

generateSequence:前の値から次を作る

println(generateSequence(1) { it * 2 }.take(10).toList())
println(generateSequence(1) { if (it < 5) it + 1 else null }.toList())

var n = 3
println(generateSequence { if (n > 0) n-- else null }.toList())   // 種なし:毎回この関数を呼ぶ
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
[1, 2, 3, 4, 5]
[3, 2, 1]
  • 第1引数が最初の値(種)、ラムダが「前の値から次の値」を作る
  • ラムダが null を返すと終わり。null を返さなければ無限に続くので、take()・takeWhile()・first() で止める
  • そのため、Sequence の要素に null は入れられない(generateSequence の場合)

sequence { yield() }:手順を書いて1件ずつ返す

fun fib() = sequence {
    var a = 0L; var b = 1L
    while (true) {
        yield(a)                    // 1件返して、次を求められるまで止まる
        val t = a + b; a = b; b = t
    }
}

println(fib().take(10).toList())
println(sequence { yield(1); yieldAll(listOf(2, 3)); yieldAll(generateSequence(10) { it + 10 }.take(2)) }.toList())
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
[1, 2, 3, 10, 20]

kotlinlang.org の説明では、yield() は要素を1つ渡して、次の要素が求められるまで sequence {} の処理を止めます。yieldAll() には Iterable・Iterator・別の Sequence を渡せます。無限の Sequence を yieldAll() に渡すと、その後ろの処理は動かないので、最後に置きます。

ページ単位の API を、必要な分だけ読む例です。

fun pages(): Sequence<List<Int>> = sequence {
    var page = 0
    while (true) {
        println("  fetch page=$page")
        val items = if (page < 3) List(2) { page * 2 + it } else emptyList()   // API の代わり
        if (items.isEmpty()) return@sequence
        yield(items)
        page++
    }
}

println(pages().flatten().take(3).toList())
println(pages().flatten().toList())
  fetch page=0
  fetch page=1
[0, 1, 2]
  fetch page=0
  fetch page=1
  fetch page=2
  fetch page=3
[0, 1, 2, 3, 4, 5]

3件だけ欲しいときは、2ページ目までしか読みませんでした。 全部読むときは、空のページ(page=3)が来たところで終わりました。sequence {} の中は普通の関数と同じように書け、return@sequence で終わります。

sequence {} の中で使えるのは yield・yieldAll だけで、delay() などの suspend 関数は呼べません。非同期で1件ずつ流すときは Flow を使います(関連記事「Kotlin Flowの使い方 StateFlowとSharedFlowの違い」で扱っています)。

中間操作と終端操作:終端操作が無いと何も動かない

val s = data.asSequence().map { println("  map($it)"); it * 10 }
println("s = $s")
s = kotlin.sequences.TransformingSequence@448139f0

map のログは1行も出ませんでした。 Sequence の map は「あとで map する」という予約を作るだけで、s は結果ではなく処理の手順です。toList()・sum()・first() などの終端操作を呼んだときに、初めて動きます。

種類 戻り値 主なもの
中間操作 Sequence map・filter・take・drop・distinct・sorted・onEach・flatMap・chunked・windowed
終端操作 値・コレクション toList・toSet・first・firstOrNull・sum・sumOf・count・forEach・joinToString・groupBy・any

ログを出したいだけなら onEach { … }(中間操作)、全部に何かしたいなら forEach { … }(終端操作)です。onEach だけで終えると、何も起きません。

同じ Sequence を2回使うと、2回とも最初から動く

println(s.count()); println(s.count())
  map(1)
  map(2)
  map(3)
  map(4)
4
  map(1)
  map(2)
  map(3)
  map(4)
4

Sequence は結果を覚えていないので、終端操作のたびに最初から処理し直しました。重い処理を何度も使うときは、一度 toList() してから使います。count() だけなのに map が動いている点にも注意します(map は件数を変えないので、本当は動かさなくても数は分かります)。

1回しか回せない Sequence

constrainOnce() を2回       → java.lang.IllegalStateException: This sequence can be consumed only once.
iterator.asSequence() を2回 → java.lang.IllegalStateException: This sequence can be consumed only once.
useLines { } の中で2回      → java.lang.IllegalStateException: This sequence can be consumed only once.
Java の Stream を2回        → java.lang.IllegalStateException: stream has already been operated upon or closed

Iterator から作った Sequence や、ファイルの行を読む useLines { } の Sequence は、1回しか回せませんでした。kotlinlang.org の Sequence の説明では、Sequence は何度でも回せるのが基本で、1回しか回せないものはドキュメントにそう書かれている、とされています。種(最初の値)を渡さない generateSequence { … } も、1回しか回せない例として挙げられています。

sorted は全件がそろうまで待つ

data.asSequence()
    .map { println("  map($it)"); it }
    .sortedDescending()
    .map { println("  after sort($it)"); it }
    .take(1).toList()
  map(1)
  map(2)
  map(3)
  map(4)
  after sort(4)
result=[4]

sorted の前は全件が流れ、後ろは1件だけでした。kotlinlang.org では、map・filter のように1件ずつ処理できる操作を stateless(状態を持たない)、sorted・distinct のように件数に比例した状態が要る操作を stateful(状態を持つ)と呼んでいます。stateful な操作の前では、Sequence でも全件を処理します。

速さの実測:件数別

4種類の処理を、List・Sequence・Java の Stream で比べました。数字は1回の処理にかかった時間の中央値(ミリ秒)で、カッコ内は1回で確保したメモリの量です。

val data = (1..n).toList()

// full:map → filter → sum(全部たどる)
data.map { it * 2 }.filter { it % 3 == 0 }.sumOf { it.toLong() }
data.asSequence().map { it * 2 }.filter { it % 3 == 0 }.sumOf { it.toLong() }

// first:map → filter → 最初の10件
data.map { it * 2 }.filter { it % 3 == 0 }.take(10).sumOf { it.toLong() }
data.asSequence().map { it * 2 }.filter { it % 3 == 0 }.take(10).sumOf { it.toLong() }

// one:map だけ → toList
data.map { it * 2 }
data.asSequence().map { it * 2 }.toList()

// five:map・filter を5段
data.map { it + 1 }.filter { it % 2 == 0 }.map { it * 3 }.filter { it % 5 != 0 }.map { it - 1 }.sumOf { it.toLong() }

map → filter → sum(全部たどる)

件数 List Sequence Stream
10 0.0007 ms(256B) 0.0003 ms(152B) 0.0005 ms(440B)
1,000 0.017 ms(23KB) 0.010 ms(15KB) 0.012 ms(15KB)
100,000 1.6 ms(2.6MB) 0.50 ms(1.6MB) 1.5 ms(1.6MB)
1,000,000 27.5 ms(24MB) 4.6 ms(16MB) 10.5 ms(16MB)
10,000,000 1039 ms(249MB) 91 ms(160MB) 90 ms(160MB)

map → filter → 最初の10件(途中で止める)

件数 List Sequence Stream
10 0.0008 ms 0.0005 ms 0.0014 ms
1,000 0.017 ms(24KB) 0.0004 ms(224B) 0.0010 ms(608B)
100,000 1.0 ms(2.6MB) 0.0002 ms(56B) 0.0005 ms(552B)
1,000,000 33.5 ms(24MB) 0.0002 ms(56B) 0.0005 ms(552B)
10,000,000 1001 ms(249MB) 0.0002 ms(56B) 0.0009 ms(552B)

map だけ → toList

件数 List Sequence Stream
10 0.0002 ms(56B) 0.0003 ms(176B) 0.0005 ms(288B)
1,000 0.011 ms(19KB) 0.016 ms(30KB) 0.007 ms(19KB)
100,000 0.51 ms(2.0MB) 0.66 ms(2.9MB) 0.81 ms(2.0MB)
1,000,000 14.8 ms(20MB) 35.0 ms(31MB) 36.3 ms(20MB)
10,000,000 719 ms(200MB) 1232 ms(326MB) 366 ms(200MB)

map・filter を5段つなぐ → sum

件数 List Sequence Stream
10 0.0012 ms 0.0010 ms 0.0026 ms
1,000 0.033 ms(49KB) 0.015 ms(28KB) 0.012 ms(28KB)
100,000 3.0 ms(5.2MB) 1.5 ms(3.0MB) 1.3 ms(3.0MB)
1,000,000 47.7 ms(51MB) 15.3 ms(30MB) 12.7 ms(30MB)
10,000,000 1360 ms(503MB) 182 ms(304MB) 154 ms(304MB)

結果から分かること

  • 途中で止める処理(first・take・any・find)は、件数が多いほど Sequence が圧倒的に速い。必要な分しか処理しないので、件数が増えても時間とメモリがほぼ変わらなかった
  • 段が多いほど Sequence が有利。List は段ごとに途中のリストを作るので、5段では確保したメモリが Sequence の約1.7倍になった
  • 1段だけで toList() するなら List の方が速かった。List の map は最初から件数が分かる大きさのリストを作るが、Sequence の toList() は件数が分からないので、リストを伸ばしながら詰める(確保したメモリも約1.5〜1.6倍)
  • 10件・1,000件では、どれも 0.03 ms 以下。少ない件数では、どちらを選んでも差は体感できない
  • Sequence と Java の Stream は、多くの場面で近い結果だった

kotlinlang.org でも、Sequence は途中の結果を作らない分速くなる一方、遅延処理のための手間(オーバーヘッド)があり、少ない件数や単純な処理では、その手間が目立つことがある、と説明されています。

測り方と注意

fun measure(rounds: Int, warm: Int, block: () -> Long): Double {
    repeat(warm) { sink += block() }          // ウォームアップ(JIT コンパイルを済ませる)
    val times = LongArray(rounds)
    for (i in 0 until rounds) {
        val t0 = System.nanoTime()
        sink += block()                       // 結果を捨てない(最適化で消されないように)
        times[i] = System.nanoTime() - t0
    }
    times.sort()
    return times[rounds / 2] / 1_000_000.0    // 中央値(ミリ秒)
}
件数 ウォームアップ 測った回数
10〜1,000 20,000回 201回
100,000 300回 51回
1,000,000 30回 21回
10,000,000 5回 11回

メモリは com.sun.management.ThreadMXBean.getThreadAllocatedBytes() で、1回の処理の前後の差を取りました。JVM のオプションは -Xms2g -Xmx2g です。

  • 同じプログラムをもう一度動かすと、数字は変わった。たとえば full の100万件は、2回目は List 41.6 ms・Sequence 5.4 ms・Stream 7.0 ms だった。1,000万件の Stream は 90 ms → 291 ms だった。GC(使わなくなったメモリの回収)が入るかどうかで大きく変わる。倍率の目安として読み、細かい差は気にしない
  • 1,000万件の List が遅いのは、途中のリスト(1段ごとに数十MB)を作り、そのたびに GC が動くため
  • Int の List なので、要素は Integer オブジェクト(箱に入った数)。map { it * 2 } のたびに新しい Integer を作る。プリミティブの IntArray や IntStream を使うと、結果は大きく変わる
  • 正確に測るなら JMH を使う。System.nanoTime() での自作の計測は、JIT(実行中の最適化)・GC・ほかのプロセスの影響を受けやすい

Java での時間の測り方は、関連記事「Java 処理時間の計測|nanoTimeとcurrentTimeMillis」で扱っています。

使うべき場面の判断表

場面 選ぶもの 理由
first・take・any・find で途中で止める Sequence 残りを処理しない
何十万件以上を、2段以上つなぐ Sequence 途中のリストを作らない
無限に続く値・ページ単位の読み込み Sequence(generateSequence・sequence {}) List では表せない
ファイルを1行ずつ読む Sequence(useLines { }) 全行をメモリに載せない
数十件〜数千件の画面用データ List 差は小さく、List の方が読みやすい
1段だけ(map だけ・filter だけ) List Sequence の方が遅かった
結果を何度も使う List(または一度 toList()) Sequence は毎回やり直す
sorted・groupBy が中心 List 全件そろうのを待つので、Sequence の利点が小さい
並列に処理したい Java の parallelStream() かコルーチン Sequence に並列の機能は無い

迷ったら List で書き、件数が多い処理や途中で止める処理だけ asSequence() を足す、という順番が書きやすい形です。List の操作一覧は、関連記事「Kotlin List・Mapの使い方とコレクション操作一覧【早見表】」で扱っています。

Java Stream との対応表

Java の Stream も、1件ずつ処理する遅延評価のしくみです。書き方の対応です。

やりたいこと Kotlin Sequence Java Stream
List から作る list.asSequence() list.stream()
変換・絞り込み map・filter map・filter
先頭 n 件・n 件飛ばす take(n)・drop(n) limit(n)・skip(n)
条件の間だけ takeWhile・dropWhile takeWhile・dropWhile(Java 9 以降)
平らにする flatMap・flatten flatMap
途中で覗く onEach peek
最初の1件 first()・firstOrNull() findFirst()(Optional)
リストにする toList() toList()(Java 16 以降)・collect(Collectors.toList())
合計 sumOf { } mapToLong(…).sum()
グループ分け groupBy { } collect(Collectors.groupingBy(…))
前の値から次を作る generateSequence(seed) { } Stream.iterate(seed, f)
手順を書いて作る sequence { yield() } 対応なし(Stream.generate・Spliterator で作る)
まとめて区切る chunked(3)・windowed(3) 標準には無い(Java 24 以降の Gatherers で書ける)
2回使う 多くの Sequence は使える(毎回やり直す) 使えない(IllegalStateException)
並列 無い parallel()
相互の変換 seq.asStream() stream.asSequence()(Kotlin 側の関数)
import kotlin.streams.asSequence
import kotlin.streams.asStream

println(listOf(3, 1, 2).asSequence().asStream().sorted().toList())
println(java.util.stream.Stream.of(1, 2, 3).asSequence().map { it + 1 }.toList())
[1, 2, 3]
[2, 3, 4]

chunked・windowed などの Sequence 独自の操作の結果です。

(1..7).asSequence().chunked(3)     → [[1, 2, 3], [4, 5, 6], [7]]
(1..5).asSequence().windowed(3)    → [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
generateSequence(1) { it + 1 }.map { it * it }.takeWhile { it < 50 }  → [1, 4, 9, 16, 25, 36, 49]

Java の Stream の書き方は、関連記事「Java Stream APIの使い方|for文からの書き換え例と注意点」で扱っています。

よくある質問

Q. List の拡張関数と Sequence の拡張関数は、名前が同じですか。 A. ほぼ同じです。kotlinlang.org でも「Sequence は Iterable と同じ関数を持ち、処理の進め方が違う」と説明されています。違いは、Sequence の map・filter が Sequence を返す点(List では List を返す)です。そのため、Sequence の最後には toList() などの終端操作が要ります。

Q. (1..1_000_000) の Range にも asSequence() は要りますか。 A. Range(IntRange)は Iterable なので、map を呼ぶと List を作ります。途中で止める処理や段の多い処理では、Range にも asSequence() を付けると途中のリストを作らずに済みます。

Q. Sequence はスレッドセーフですか。 A. Sequence は1つのスレッドで順に処理するしくみで、並列の機能はありません。複数のスレッドで同じ Iterator 由来の Sequence を回す使い方は避けます。並列処理は関連記事「Kotlinコルーチン入門|launch・async・runBlocking」で扱っています。

Q. ログを入れたら処理の順番が変わって見えます。 A. Sequence では、各段のラムダが要素ごとに交互に呼ばれます。上のログのように「map(1) → filter(10) → map(2) …」と並ぶのは正しい動きです。List と同じ順番を期待していると、ログの読み違いが起きます。

まとめ

  • Sequence は1件ずつ全段を通す遅延評価。List は段ごとに途中のリストを作る
  • 終端操作(toList・first・sum など)を呼ぶまで、何も動かない。2回使うと2回やり直す
  • 作り方は asSequence()・generateSequence()・sequence { yield() }。generateSequence は null で終わる
  • 途中で止める処理は、100万件で List 33.5 ms・Sequence 0.0002 ms。段の多い処理でも Sequence が速かった
  • 1段だけで toList() するなら List の方が速かった。少ない件数では差は小さい
  • sorted・distinct の前では、Sequence でも全件を処理する
  • Java Stream とほぼ同じ考え方。違いは、Sequence は再利用でき、並列が無く、chunked・sequence {} がある点

参考・出典

確認日はいずれも 2026年10月13日です。

  • Kotlin「Sequences」:https://kotlinlang.org/docs/sequences.html
  • Kotlin API「Sequence」:https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.sequences/-sequence/
  • Kotlin API「generateSequence」:https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.sequences/generate-sequence.html
  • Kotlin API「sequence」:https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.sequences/sequence.html
  • Java SE 21「java.util.stream」:https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/package-summary.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Kotlin Sequenceの使い方|Listとの違いと速さを実測」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?