倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

小川のせせらぎもやがてはうねる奔流に Stream インターフェース (7) - static メソッド -

Java の Stream インターフェースに定義されているメソッドを見ていくシリーズ(目次)。 今回は Stream インターフェースに定義されている static メソッドを見ていきます。 というか、Java 8 からインターフェースに static メソッドが定義できるようになったんですな。 Stream インターフェースに定義されている static メソッドはどれも Stream オブジェクトを生成する static ファクトリメソッドです。 なんか、このシリーズ、「Reduce 処理 → Map 処理 → Stream の生成」と、記事の順序がおもいっきり逆な気がしますが・・・ 今更ですな。 まぁそれはおいといて、今回扱うメソッドは以下の通り:

static <T> Stream<T> empty()
static <T> Stream<T> of(T t)
static <T> Stream<T> of(T... values)
static <T> Stream<T> generate(Supplier<T> s)
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
static <T> StreamBuilder<T> builder()

Stream オブジェクトを生成する方法はこれらの方法以外に、java.util.Collection オブジェクトに対しては Java 8 で新たに定義された stream(), parallelStream() メソッドを呼び出すか、配列に対しては java.util.Arrays クラスに追加された static メソッド stream() を呼び出す、という方法があります。 後で見ますが、配列の場合は Stream#of() メソッドでも可能です(全ての要素からなる Stream オブジェクトを生成する場合)。 では各メソッドを見ていきましょう。

empty() メソッド

まずは空ストリームを生成する empty() メソッド:

Stream<String> stream0 = Stream.empty();
assert stream0.count() == 0;

まぁ、型推論などもされるので簡単。 Collections クラスに Collection に対する似たような static メソッドありましたね。

of() メソッド

次は要素を列挙して Stream オブジェクトを生成する of() メソッド。 引数1つの場合は1つの要素からなるシングルトン・ストリーム(っていうのかな?)を生成します。 Collection に対する Collections#singleton(), singletonList() などに対応します。 可変長引数の of() メソッドはそのままそれらを要素とする Stream オブジェクトを返します:

// of(T)
Stream<String> stream1 = Stream.of("Java");
assert stream1.count() == 1;

// of(T...)
Stream<String> stream2 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");
assert stream2.count() == 7;

// 配列を渡してもいいよ
String[] strArray = {"Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby"};
Stream<String> stream3 = Stream.of(strArray);
assert stream3.count() == 7;

今までの記事のサンプルでもこれを多用してました。

generate() メソッド

次は要素数が無限個のストリームを生成する generate() メソッド。 引数に Supplier<T> オブジェクトをとります。 まずは0から100(0以上100未満)までの整数乱数を返すストリーム:

// 乱数
Random random = new Random(1L);
Stream<Integer> rand100 = Stream.generate(() -> random.nextInt(100));
rand100.limit(30).forEach(System.out::println);

無限に要素があるので、limit() メソッドによって30個に制限しています。 まぁ、なんてことないですね。  generate() の引数の Supplier オブジェクトは、もちろんラムダ式で書かないといけないわけではないので、以下のように無名クラスでオブジェクトを作ることもできます。 以下はよくありそうなフィボナッチ数列を生成するストリームを作るサンプル:

// フィボナッチ数列
Stream<Long> fib1 = Stream.generate(new Supplier<Long>(){

    private long a = 0L, b = 1L;

    @Override
    public Long get(){
        long newA = b, newB = a + b;
        a = newA; b = newB;
        return a;
    }
});

fib1.limit(30).forEach(System.out::println);

LongStream でやった方がスッキリしそうだけど、まぁそんなに変わるってわけでもないのでご愛嬌。 出力結果は

1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
832040

えぇー、あってるよね? ちなみにこれをラムダ式

long x = 0L, y = 1L;
Stream<Long> fib0 = Stream.generate(() -> {
    long newX = y, newY = x + y;
    x = newX; y = newY;
        return x;
});

のように書こうとすると「ラムダ式から参照されるローカル変数は、finalまたは事実上のfinalである必要があります」という旨のメッセージとともにコンパイル・エラーを起こします。 まぁメッセージの通りですがね。 詳しくはid:nowokay 氏の「Java8のlambda構文がどのようにクロージャーではないか」参照のこと。 まぁ、Supplier に状態を持たせたい場合は(無名)クラスを書けということでしょうかね。

iterate() メソッド

iterate() メソッドも generate() メソッド同様、無限要素数のストリームを生成するメソッドです。 iterate() メソッドは2つの引数をとり、第1引数はストリームの初期値、第2引数は前の値から次の値を計算する漸化式をとります。 漸化式の部分は UnaryOperator<T> オブジェクトとして指定します。 まずは初期値1で、値を10倍していくストリーム(初項1、公比10の等比数列):

// 10倍を繰り返して返す
Stream<Integer> multi10 = Stream.iterate(1, i -> i * 10);
multi10.limit(5).forEach(System.out::println);

実行結果は

1
10
100
1000
10000

簡単ですね。 次はもう少し複雑な(でもよくある)サンプル。 またしてもフィボナッチ数列ですが:

// フィボナッチ数列 その2
Stream<Long> fib2 = Stream.iterate( new Long[]{ 1L, 1L }, f -> new Long[]{ f[1], f[0] + f[1] }).map( f -> f[0] );
fib2.limit(30).forEach(System.out::println);

iterate() で生成しているストリームは要素数2の Long 配列で、そのストリームを生成した後、map() メソッドでその配列の第0要素を取り出しています。 ラムダ式では状態を保持しにくい(できない)ので(よくやられる)テクニカルな方法をやってみました。 出力結果はもちろん generate() の場合のフィボナッチ数列のサンプルと同じです。

builder() メソッド

最後は StreamBuilder オブジェクトを使ってストリームを生成する方法。 StreamBuilder は不変な String に対する可変な StringBuilder, StringBuffer に対応するインターフェースです*1。 使い方は

  1. Stream#builder() メソッドによって StreamBuilder オブジェクトを生成する
  2. add() メソッド(もしくは accept() メソッド、効果は同じ)によって要素を追加
  3. build() メソッドによって Stream オブジェクトを生成

という手順になります。 build() メソッドによってストリームを生成した後は要素を追加することはできません。 サンプルはこんな感じ:

List<String> langs = Arrays.asList("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");

StreamBuilder<String> builder = Stream.builder();
for(String lang : langs)
    builder.add(lang);

Stream<String> stream4 = builder.build();
stream4.forEach(System.out::println);

出力結果は

Java
Groovy
Scala
Clojure
Kotlin
Jython
JRuby

と、まぁそのままな感じ。 今の場合は Collection#stream() メソッドで同じことが簡単にできますが、各要素を生成するのに手間がかかる、別々に作るといった場合に使えます。

今回で Stream インターフェースに定義されているメソッドは完了。 次回はプリミティブ型のストリーム IntStream, LongStream, DoubleStream に特有のメソッドを見ていく予定。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

*1:並行性に関することが書かれていないので、おそらく StringBuilder に対応するものかと。