HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

IntStreamで遊んでみた

暇だったので、Java 8から追加されたjava.util.stream.IntStreamでちょっと遊んでみた。

ちなみに、IntStreaminterfaceであるが、ソースを見るとstaticメソッドの実装が書いてある。IntStream#ofとか。まぁそれは余談。

以下のようなソースで出力を見ていく。

import java.util.Arrays;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        int[] ary = { 5, 2, 8, 2, 9, 1, 3, 0, 5, 6, 4, 7, };

        System.out.println("--------countTest");
        countTest(ary);
        System.out.println("--------sumTest");
        sumTest(ary);
        System.out.println("--------maxTest");
        maxTest(ary);
        System.out.println("--------minTest");
        minTest(ary);
        System.out.println("--------sortTest");
        sortTest(ary);
        System.out.println("--------1つのstreamで連続処理できるのかテスト");
        test(ary);
        System.out.println("--------パフォーマンスを計ってみる(sumを例に)");
        for (int ct = 1; ct <= 100000000; ct *= 10) {
            performance(ary, ct);
        }
        System.out.println("--------その他");
        othersTest(ary);
        System.out.println("--------classTest");
        classTest(ary);
    }
    private static void countTest(int[] ary) {
        // 配列が手元にある場合に最もポピュラーな手段
        System.out.println("count1=" + ary.length);

        // Streamしか手元にない場合は仕方ない
        IntStream stream = IntStream.of(ary);
        System.out.println("count2=" + stream.count());
    }
    private static void sumTest(int[] ary) {
        // ループ変数を使って要素にアクセス
        int sum1 = 0;
        for (int i = 0; i < ary.length; ++i) {
            sum1 += ary[i];
        }
        System.out.println("sum1=" + sum1);

        // 拡張for文
        int sum2 = 0;
        for (int a : ary) {
            sum2 += a;
        }
        System.out.println("sum2=" + sum2);

        // java.util.Arrays.stream()
        System.out.println("sum3=" + Arrays.stream(ary).sum());

        // java.util.stream.IntStream
        System.out.println("sum4=" + IntStream.of(ary).sum());
    }
    private static void maxTest(int[] ary) {
        // ループ変数を使って要素にアクセス
        int max1 = Integer.MIN_VALUE;
        for (int i = 0; i < ary.length; ++i) {
            max1 = Math.max(max1, ary[i]);
        }
        System.out.println("max1=" + max1);

        // 拡張for文
        int max2 = Integer.MIN_VALUE;
        for (int a : ary) {
            max2 = Math.max(max2, a);
        }
        System.out.println("max2=" + max2);

        // java.util.Arrays.stream()
        System.out.println("max3=" + Arrays.stream(ary).max());

        // java.util.stream.IntStream
        System.out.println("max4=" + IntStream.of(ary).max());
    }
    private static void minTest(int[] ary) {
        // ループ変数を使って要素にアクセス
        int min1 = Integer.MAX_VALUE;
        for (int i = 0; i < ary.length; ++i) {
            min1 = Math.min(min1, ary[i]);
        }
        System.out.println("min1=" + min1);

        // 拡張for文
        int min2 = Integer.MAX_VALUE;
        for (int a : ary) {
            min2 = Math.min(min2, a);
        }
        System.out.println("min2=" + min2);

        // java.util.Arrays.stream()
        System.out.println("min3=" + Arrays.stream(ary).min());

        // java.util.stream.IntStream
        System.out.println("min4=" + IntStream.of(ary).min());
    }
    private static void sortTest(int[] ary) {
        // java.util.Arraysのsortメソッド(副作用あり)
        int[] ary1 = ary.clone();
        Arrays.sort(ary1);
        System.out.println("sort1=" + Arrays.toString(ary1));

        // java.util.stream.IntStreamのソート済みstreamを取得するsortedメソッド(副作用無し)
        IntStream stream2 = IntStream.of(ary).sorted();
        System.out.println("sort2=" + Arrays.toString(stream2.toArray()));
        System.out.println("orig =" + Arrays.toString(ary));
    }
    private static void test(int[] ary) {
        try {
            IntStream stream = IntStream.of(ary);
            System.out.println("sumT=" + stream.sum());
            System.out.println("maxT=" + stream.max());
            System.out.println("minT=" + stream.min());
            System.out.println("sortT=" + Arrays.toString(stream.sorted().toArray()));
        } catch (RuntimeException e) {
            e.printStackTrace();
        }
    }
    private static void performance(int[] ary, int loopCount) {
        // ループ変数を使って要素にアクセス
        long S1 = System.currentTimeMillis();
        for (int ct = 0; ct < loopCount; ++ct) {
            int sum1 = 0;
            for (int i = 0; i < ary.length; ++i) {
                sum1 += ary[i];
            }
        }
        long G1 = System.currentTimeMillis();

        // 拡張for文
        long S2 = System.currentTimeMillis();
        for (int ct = 0; ct < loopCount; ++ct) {
            int sum2 = 0;
            for (int a : ary) {
                sum2 += a;
            }
        }
        long G2 = System.currentTimeMillis();

        // java.util.Arrays.stream()
        long S3 = System.currentTimeMillis();
        for (int ct = 0; ct < loopCount; ++ct) {
            int sum3 = Arrays.stream(ary).sum();
        }
        long G3 = System.currentTimeMillis();

        // java.util.stream.IntStream
        long S4 = System.currentTimeMillis();
        for (int ct = 0; ct < loopCount; ++ct) {
            int sum4 = IntStream.of(ary).sum();
        }
        long G4 = System.currentTimeMillis();

        System.out.printf("sum(ms) %16dct | %5d | %5d | %5d | %5d |%n",
            loopCount, G1 - S1, G2 - S2, G3 - S3, G4 - S4);
    }
    private static void othersTest(int[] ary) {
        System.out.println("orig    =" + Arrays.toString(ary));
        // distinct
        IntStream stream1 = IntStream.of(ary).distinct();
        System.out.println("distinct=" + Arrays.toString(stream1.toArray()));
        // skip
        IntStream stream2 = IntStream.of(ary).skip(1);
        System.out.println("skip    =" + Arrays.toString(stream2.toArray()));
        // limit
        IntStream stream3 = IntStream.of(ary).limit(5);
        System.out.println("limit   =" + Arrays.toString(stream3.toArray()));
    }
    private static void classTest(int[] ary) {
        IntStream stream;

        stream = IntStream.empty();
        System.out.println("empty     =" + stream.getClass().getName());
        stream = IntStream.range(1, 2);
        System.out.println("range     =" + stream.getClass().getName());
        stream = IntStream.concat(IntStream.empty(), IntStream.empty());
        System.out.println("concat    =" + stream.getClass().getName());
        stream = IntStream.of(ary);
        System.out.println("of        =" + stream.getClass().getName());
        stream = IntStream.of(ary).sorted();
        System.out.println("sorted    =" + stream.getClass().getName());
        stream = IntStream.of(ary).filter(i -> true);
        System.out.println("filter    =" + stream.getClass().getName());
        stream = IntStream.of(ary).map(i -> i);
        System.out.println("map       =" + stream.getClass().getName());
        stream = IntStream.of(ary).distinct();
        System.out.println("distinct  =" + stream.getClass().getName());
        stream = IntStream.of(ary).skip(1);
        System.out.println("skip      =" + stream.getClass().getName());
        stream = IntStream.of(ary).limit(1);
        System.out.println("limit     =" + stream.getClass().getName());
    }
}

各々の出力結果

countTest

--------countTest
count1=12
count2=12

int配列が手元にあるなら要らないだろうというメソッドcount()

Streamしか手元にないなら仕方ないだろうが、Streamの要素を数え上げるという処理によって要素が消費されてその後は何もできなくなる(後述)ので、要素数を取得してStreamを捨ててもよいという場合にしか使えない。

sumTest

--------sumTest
sum1=52
sum2=52
sum3=52
sum4=52

これは、配列の要素のsumを計算するのに1行で書けるのでちょっとうれしいかも。

ちなみに、IntStream.of(int...)は内部でArrays.stream(int[])を呼んでいる。

maxTest

--------maxTest
max1=9
max2=9
max3=OptionalInt[9]
max4=OptionalInt[9]

これは、OptionalIntが返ってくるのがちょっと微妙なところ。max値がInteger.MIN_VALUEかどうかと比較するのとどっちがいいかという感じ。

minTest

--------minTest
min1=0
min2=0
min3=OptionalInt[0]
min4=OptionalInt[0]

maxと同じく。

sortTest

--------sortTest
sort1=[0, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9]
sort2=[0, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9]
orig =[5, 2, 8, 2, 9, 1, 3, 0, 5, 6, 4, 7]

元の配列への副作用を発生させないために、ary.clone()するかIntStream#sortedを使うかという感じ。ソートされたStreamを取得して更にあれこれやりたい場合には有用。

test

--------1つのstreamで連続処理できるのかテスト
sumT=52
java.lang.IllegalStateException: stream has already been operated upon or closed
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
        at java.util.stream.IntPipeline.reduce(IntPipeline.java:461)
        at java.util.stream.IntPipeline.max(IntPipeline.java:424)
        at Main.test(Main.java:111)
        at Main.main(Main.java:19)

これはまぁ予想通り。java.io.InputStreamやjava.io.Readerからの読み込みを(一部を除いて)巻き戻し再実行できないのと同じ。

(補足)sum、max、min、average、countが欲しいだけならsummaryStatistics()メソッドを使えばいいらしい。

performance

--------パフォーマンスを計ってみる(sumを例に)
sum(ms)                1ct |     0 |     0 |     0 |     0 |
sum(ms)               10ct |     0 |     0 |     0 |     0 |
sum(ms)              100ct |     0 |     0 |     1 |     0 |
sum(ms)             1000ct |     0 |     0 |     9 |     6 |
sum(ms)            10000ct |     2 |     2 |     4 |    16 |
sum(ms)           100000ct |     5 |    28 |     8 |    18 |
sum(ms)          1000000ct |    75 |    66 |   102 |    53 |
sum(ms)         10000000ct |     0 |     0 |   649 |   607 |
sum(ms)        100000000ct |     0 |     0 |  6118 |  6154 |

まぁ、IntStreamオブジェクトを生成するから仕方ない。

othersTest

--------その他
orig    =[5, 2, 8, 2, 9, 1, 3, 0, 5, 6, 4, 7]
distinct=[5, 2, 8, 9, 1, 3, 0, 6, 4, 7]
skip    =[2, 8, 2, 9, 1, 3, 0, 5, 6, 4, 7]
limit   =[5, 2, 8, 2, 9]

distinctは重複要素を除去した新たなStreamを生成する。

skipは先頭の指定された要素数をスキップした新たなStreamを生成する。

limitは先頭の指定された要素数だけを返す新たなStreamを生成する。

その他にfilterとかmapとか色々あるが割愛(謎)。lambda式から逃げてるだけとも言わなくもない(ぉ‥

classTest

--------classTest
empty     =java.util.stream.IntPipeline$Head
range     =java.util.stream.IntPipeline$Head
concat    =java.util.stream.IntPipeline$Head
of        =java.util.stream.IntPipeline$Head
sorted    =java.util.stream.SortedOps$OfInt
filter    =java.util.stream.IntPipeline$9
map       =java.util.stream.IntPipeline$3
distinct  =java.util.stream.ReferencePipeline$4
skip      =java.util.stream.SliceOps$2
limit     =java.util.stream.SliceOps$2

java.util.stream.IntStreamがインタフェースだということで、実装クラスがどうなっているのか気になったので列挙してみた。