倭マン's BLOG

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

小川のせせらぎもやがてはうねる奔流に Stream インターフェース (6) - stateful intermediate operators -

Java の Stream インターフェースに定義されているメソッドを見ていくシリーズ(目次)。 今回は Stream インターフェースに定義されている stateful intermediate operator に対応するメソッドを見ていきます。 stateful intermediate oprerator は「状態保有中間演算子」と訳せばいいんでしょうか。 「中間演算子」なので、返り値として新たな Stream オブジェクトを返すのは前回見た演算子と同じですが、新たなストリームを返すのに元のストリームの要素の情報を保持する必要があるため「状態保有」となっています。 例えば sort() メソッドは自然順序で要素を並べたストリームを返しますが、これには全要素に対して順序を比べる処理をする必要がありますね。 したがって、状態保有中間演算子を使う場合は、元のストリームが無限要素数のストリームであったり、並行ストリームであったりする場合に注意が必要です。

今回扱うもう一つの演算子のカテゴリは short-circiting stateful intermediate operator で、これは上記の stateful intermediate operator の性質に加えて、ショートカット演算、つまり全ての要素を処理しきらずに処理を終える(場合がある)という性質を持った演算子です。 Java 演算子の && などと同じような処理をします。

今回見ていくメソッドは以下の通り:

// Stateful Intermediate Operator
Stream<T> distinct()
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
Stream<T> substream(long startInclusive)

// Short-circuiting Stateful Intermediate Operator
Stream<T> substream(long startInclusive, long endExclusive)
Stream<T> limit(long maxSize)

それぞれ簡単なサンプルを見ていきましょう。

distinct() メソッド
distinct() メソッドは同じ(多分 Object#equals() メソッドで true が返される)オブジェクトを最初の1つ以外省いたストリームを返します。 Object#equals() を同値関係とみた商集合(のストリーム)といった感じでしょうか。

Stream<String> stream = Stream.of("Java", "Groovy", "Java", "Scala", "Groovy", "Clojure", "Java");

stream.distinct().forEach(System.out::println);

これを実行すると

Java
Groovy
Scala
Clojure

という風に、同じ文字列は最初の1回だけしか出力されません。

sorted() メソッド
sorted() メソッドは前回見た unordered() メソッドの逆で、元のストリームの全要素を(自然、もしくは指定した)順序に従って整列させたストリームを返します:

Stream<String> stream = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");

stream().sorted().forEach(System.out::println);

引数なしの sort() は自然順序、すなわちオブジェクトが実装している Comparable インターフェースに従った順序によって整列させます。 今の場合、String オブジェクトの自然順序は辞書順序なので、出力は次のような ABC 順に並びます:

Clojure
Groovy
JRuby
Java
Jython
Kotlin
Scala

また、sorted() メソッドの引数として Comparator オブジェクトを指定して、好きな順序で整列させることもできます。 例えば文字列の長さで整列させたい場合は、以下のようにします:

Stream<String> stream = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");

stream().sorted((s1, s2) -> s1.length() - s2.length()).forEach(System.out::println);

ラムダ式に慣れれば Comparator オブジェクト作るのも難しくないですね。 これを実行すると

Java
Scala
JRuby
Groovy
Kotlin
Jython
Clojure

のようになります。

substream() メソッド
substream() メソッドは元のストリームの部分ストリームを返します。 引数が1つの場合はそれ以降の要素からなるストリーム(指定した番号*1の要素は含む)、引数が2つの場合はその範囲で指定されるストリーム(開始番号の要素は含み、終了番号の要素は含まない)を返します。 引数が1つの場合は状態保有中間演算子、引数が2つの場合はショートカット状態保有中間演算子になります。 まずは引数が1つの場合のサンプルコードから(引数が long 値なので「2L」としてますが、別に「2」で OK):

Stream<String> stream = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");

stream.substream(2L).forEach(System.out::println);

実行すると以下のようになります:

Scala
Clojure
Kotlin
Jython
JRuby

2番目の "Scala" は返されるストリームに含まれます。 次は引数が2つの場合:

Stream<String> stream = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");

stream.substream(2L, 4L).forEach(System.out::println);

実行すると

Scala
Clojure

となり、2番目の "Scala" は含まれますが、4番目の "Kotlin" は含まれません。

limit() メソッド
最後は limit() メソッド。 これは substream() メソッドと似ていますが、引数が1つの substream() とは異なり、引数の番号(0から始まる)までの要素をストリームとして返します。 ただし、引数が2つの substream() メソッドの第2引数と同様、その指定した番号の要素は含みません。 このメソッドはショートカット状態保有中間演算子です。 サンプルコードはこんな感じ:

Stream<String> stream = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");

stream.limit(4L).forEach(System.out::println);

実行すると

Java
Groovy
Scala
Clojure

となり、第0要素 "Java" から第3要素 "Clojure" までを含んだストリームを返します。

次回は Stream オブジェクトを生成するために使う、Stream インターフェースに定義された static メソッドを見ていきます・・・これ、最初にやるべきだったよなぁw

Scalaスケーラブルプログラミング第2版

Scalaスケーラブルプログラミング第2版

*1:もちろん、この番号は0から始まりますよ。