daisuzz.log

KotlinのCollections APIとSequence, Java8のStream APIの評価のタイミングについて

最近はkotlin-puzzlersというGitHubリポジトリの問題を見ながらKotlinを勉強しています。 今回は、kotlin-puzzlersの中で取り上げられていた問題の一つを紹介していきます。

問題

fun main(vararg args: String) {
    val x = listOf(1, 2, 3).filter { print("$it "); it >= 2 }
    print("before sum ")
    println(x.sum())
}

↑のコードを実行すると、なにが標準出力されるか?という問題。 選択肢は、以下の4つ

  • 1 2 3 before sum 5

  • 2 3 before sum 5

  • before sum 1 2 3 5

  • order is not deterministic

解説

答えは、「1 2 3 before sum 5」です。

問題のコードはCollections APIの結果を変数に格納しています。 KotlinのCollections APIは、宣言した箇所で評価されるため、変数宣言時に「1 2 3」が出力されます。 その後、「before sum」が出力され、最後のx.sum()で「5」が出力されます。

問題のコードを以下のようにSequenceに変換すると、実行結果が「before sum 1 2 3 5」に変わります。

fun main(vararg args: String) {
    val x = listOf(1, 2, 3).asSequence().filter { print("$it "); it >= 2 }
    print("before sum ")
    println(x.sum())
}

KotlinのSequenceは値を返す終端操作が実行されるときになってはじめて評価されます。 このコードでは、変数宣言時には標準出力がされず、その次の行の「before sum」が最初に出力されます。 その次にx.sum()を実行するタイミングで「1 2 3」が出力され、最後に「5」が出力されます。

この動きは、Java8のStream APIをつかったものと全く同じ動きになります。 以下のようにStream APIに変換したコードでもSequenceと同じ出力結果「before sum 1 2 3 5」になります。

public class Main {
    public static void main(String[] args) {
        Stream<Integer> list = Arrays.asList(1, 2, 3).stream()
                .filter(integer -> {
                    System.out.println(integer + " ");
                    return integer >= 2;
                });
        System.out.println("before sum");
        System.out.println(list.mapToInt(Integer::intValue).sum());
    }
}

おわりに

今回は、KotlinのCollections APIとSequence, Java8のSteream APIの評価のタイミングについて書いてきました。