倭マン's BLOG

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

蒐集してやんよ java.util.stream.Collectors クラス (3) - 内蔵コレクタの特性 -

Java の Collectors クラスに定義されているメソッドを見ていくシリーズ(目次)。 前回までで java.util.stream.Collectors クラスで取得できる Collector オブジェクトの使い方を見ました。 そこでは基本的な使い方を見ていたので Collector インターフェース自体の API には触れてませんでした。 なので、それらの API を改めて見ていたのですが、コレクタには特性 (characteristics) というプロパティ(?)があるようです:

package java.util.stream;

public interface Collector<T,R>{

    ... // いくつかのメソッド

    Set<Collector.Characteristics> characteristics();

    public static enum Characteristics{
        CONCURRENT,
        STRICTLY_MUTATIVE,
        UNORDERED
    }
}

コレクタの特性は Collector.Characteristics という定数(列挙型)で定義されています。 この定数には(Java 8 ea-b90 では)3つの値が定義されています:

定数 説明
CONCURRENT 並列処理を行う
STRICTLY_MUTATIVE コレクト処理の結果として返す(コンテナ)オブジェクトが可変な(状態を持つ)オブジェクト
UNORDERED コレクト処理の結果に定まった順序を持たない
  • STRICTLY_MUTATIVE は、コレクト結果が StringBuilder, StringJoiner や(可変)コレクション型のような場合に返されます。
  • CONCURRENT はストリームの各要素に対して並行処理を行ってコレクト処理の結果として、その並行処理の結果を含んだ(コンテナ)オブジェクトを返すので STRICTLY_MUTATIVE でもあります。
  • Collectors クラスの static メソッドが返す CONCURRENT のコレクタは UNORDERED でもあるようです。 並行処理を行うと処理順序が不定なので大抵の場合は CONCURRENT なら UNORDERED になるかと。

以上を踏まえて、前回までで見た Collectors クラスの static メソッドが返すコレクタの特性をまとめると下表のようになります:

コレクタ CONCURRENT STRICTLY_MUTATIVE UNORDERED
mapping(mapper, collector)
reducing(op)
reducing(identity, op)
reducing(identity, mapper, op)
toStringBuilder()
toStringJoiner(delim)
   ○
   ○
toCollection()
toList()
toSet()
   ○

   ○


   ○
counting()
sumBy(mapper)
maxBy(comparater)
minBy(comparater)
toIntSummaryStatistics(mapper)
toLongSummaryStatistics(mapper)
toDoubleSummaryStatistics(mapper)
   ○
   ○
   ○
toMap(mapper, mapper)
toMap(mapper, mapper, merger)
toMap(mapper, mapper, merger, factory)
   ○
   ○
   ○
toConcurrentMap(mapper, mapper)
toConcurrentMap(mapper, mapper, merger)
toConcurrentMap(mapper, mapper, merger, factory)
   ○
   ○
   ○
   ○
   ○
   ○
   ○
   ○
   ○
partitioningBy(predicate)
partitioningBy(predicate, collector)
   ○
   ○
groupingBy(classifier)
groupingBy(classifier, collector)
groupingBy(classifier, factory, collector)
   ○
   ○
   ○
groupingByConcurrent(classifier)
groupingByConcurrent(classifier, collector)
groupingByConcurrent(classifier, factory, collector)
   ○
   ○
   ○
   ○
   ○
   ○
   ○
   ○
   ○
  • CONCURRENT 特性を持つメソッドはメソッド名に「Concurrent」が付いているものだけのようです。 それらは STRICTLY_MUTATIVE であり UNORDERED であります。
  • UNORDERED ではあるけど CONCURRENT ではないのは toSet() だけのようです。
  • toList() が STRICTLY_MUTATIVE でないのは?
  • static ファクトリメソッドの引数に別のコレクタを渡して生成するコレクタは、渡すコレクタの特性によってその特性が変化したりしないかちょっと微妙ですが、いくつか試した中では変わりませんでした(そんなにたくさん試したわけではありませんが)。

次回こそ独自コレクタを作ろうか・・・

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

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

蒐集してやんよ java.util.stream.Collectors クラス (2) - Map を返す Collector -

Java の Collectors クラスに定義されているメソッドを見ていくシリーズ(目次)。 前回に引き続き、今回は Map を返す Collector を見ていきます。 前回にも増して引数の型パラメータを見てるとクラクラするぅ。 まぁ、前回同様、返り値の Collector の第2型パラメータあたりを中心に見ておけばいいんじゃないかなぁ。 今回扱うのはこの部分が Map もしくはそれを拡張した型になってます。

今回扱うのは以下のメソッド:

//***** toMap() 関連 *****
// toMap()
static <T,K,U> Collector<T,Map<K,U>>
toMap(Function<? super T,? extends K> keyMapper,
      Function<? super T,? extends U> valueMapper)

static <T,K,U> Collector<T,Map<K,U>>
toMap(Function<? super T,? extends K> keyMapper,
      Function<? super T,? extends U> valueMapper,
      BinaryOperator<U> mergeFunction)

static <T,K,U,M extends Map<K,U>> Collector<T,M>
toMap(Function<? super T,? extends K> keyMapper,
      Function<? super T,? extends U> valueMapper,
      BinaryOperator<U> mergeFunction,
      Supplier<M> mapSupplier)

// toConcurrentMap()
static <T,K,U> Collector<T,ConcurrentMap<K,U>>
toConcurrentMap(Function<? super T,? extends K> keyMapper,
                Function<? super T,? extends U> valueMapper)

static <T,K,U> Collector<T,ConcurrentMap<K,U>>
toConcurrentMap(Function<? super T,? extends K> keyMapper,
                Function<? super T,? extends U> valueMapper,
                BinaryOperator<U> mergeFunction)

static <T,K,U,M extends ConcurrentMap<K,U>> Collector<T,M>
toConcurrentMap(Function<? super T,? extends K> keyMapper,
                Function<? super T,? extends U> valueMapper,
                BinaryOperator<U> mergeFunction,
                Supplier<M> mapSupplier)

// Merger
static <T> BinaryOperator<T> firstWinsMerger()
static <T> BinaryOperator<T> lastWinsMerger()
static <T> BinaryOperator<T> throwingMerger()

//***** 分割・グループ化 *****
// partitioningBy()
static <T> Collector<T,Map<Boolean,List<T>>>
partitioningBy(Predicate<? super T> predicate)

static <T,D> Collector<T,Map<Boolean,D>>
partitioningBy(Predicate<? super T> predicate,
               Collector<? super T,D> downstream)

// groupingBy()
static <T,K> Collector<T,Map<K,List<T>>>
groupingBy(Function<? super T,? extends K> classifier)

static <T,K,D> Collector<T,Map<K,D>>
groupingBy(Function<? super T,? extends K> classifier,
           Collector<? super T,D> downstream)

static <T,K,D,M extends Map<K,D>> Collector<T,M>
groupingBy(Function<? super T,? extends K> classifier,
           Supplier<M> mapFactory,
           Collector<? super T,D> downstream)

// groupingByConcurrent()
static <T,K> Collector<T,ConcurrentMap<K,List<T>>>
groupingByConcurrent(Function<? super T,? extends K> classifier)

static <T,K,D> Collector<T,ConcurrentMap<K,D>>
groupingByConcurrent(Function<? super T,? extends K> classifier,
                     Collector<? super T,D> downstream)

static <T,K,D,M extends ConcurrentMap<K,D>> Collector<T,M>
groupingByConcurrent(Function<? super T,? extends K> classifier,
                     Supplier<M> mapFactory,
                     Collector<? super T,D> downstream)

xxxConcurrent() は並行実行する以外は xxx() というメソッドと変わらないと思うので省略。

toMap() メソッド

まずは toMap() メソッド。 3つのオーバーロードされたシグニチャがあります:

static <T,K,U> Collector<T,Map<K,U>>
toMap(Function<? super T,? extends K> keyMapper,
      Function<? super T,? extends U> valueMapper)

static <T,K,U> Collector<T,Map<K,U>>
toMap(Function<? super T,? extends K> keyMapper,
      Function<? super T,? extends U> valueMapper,
      BinaryOperator<U> mergeFunction)

static <T,K,U,M extends Map<K,U>> Collector<T,M>
toMap(Function<? super T,? extends K> keyMapper,
      Function<? super T,? extends U> valueMapper,
      BinaryOperator<U> mergeFunction,
      Supplier<M> mapSupplier)

引数として2つの Function をとるメソッドは各要素からマップのキーと値を生成して Map オブジェクトを構築するというメソッド。 これはまぁ基本というか素直なメソッドですね。 更に BinaryOperator 型の引数をとるメソッドはキーとして同じオブジェクトが現れたときに値をどうするか?を指定します。 これはもちろんどんな BinaryOperator オブジェクトでもいいんですが、通常は Collectors クラスに定義されている static メソッド

static <T> BinaryOperator<T> firstWinsMerger()    // 先に現れた方を優先
static <T> BinaryOperator<T> lastWinsMerger()    // 後に現れた方を優先
static <T> BinaryOperator<T> throwingMerger()    // IllegalStateException を投げる(同じキーが現れることを許さない)

のいずれかを指定することが多いのではないかと。 更に更に Supplier オブジェクトを指定するメソッドは、返される Map の実装として何を用いるのかを指定します。 では簡単なサンプルコード:

import static java.util.stream.Collectors.*;

// toMap(keyMapper, valueMapper)
Stream<String> stream0 = Stream.of("Java", "Groovy", "Scala", "Clojure");
Map<String, Integer> lengthMap = stream0.collect(toMap(s -> s, s -> s.length()));
    // (文字列, 文字列の長さ) のペアのマップ
assert lengthMap.get("Java") == 4;
assert lengthMap.get("Scala") == 5;

// toMap(keyMapper, valueMapper, merger)
Stream<String> stream1 = Stream.of("Java", "Groovy", "Groovy", "Scala", "Java", "Clojure", "Java");
Map<String, Integer> countMap = stream1.collect(toMap(s -> s, s -> 1, (i, j) -> i + j));    // merger 使ってない・・・
    // (文字列, 文字列の出現数) のペアのマップ
assert countMap.get("Java") == 3;
assert countMap.get("Clojure") == 1;

// toMap(keyMapper, valueMapper, merger, mapSupplier)
Stream<String> stream2 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");
Map<String,String> headMap = stream2.collect(toMap(s->s.substring(0, 1), s->s, firstWinsMerger(), TreeMap::new));
    // (頭文字, 文字列) のペアのマップ
assertString(headMap.get("J"), "Java");
assertString(headMap.get("G"), "Groovy");

/** 文字列同士のアサーション */
public static void assertString(String s0, String s1){
    assert s0.equals(s1);
}

第3引数の BinaryOperator には Collections の static メソッドを使った merger を使うことが多いのでは、と言っておいて上記のサンプルでは使ってないというこの天の邪鬼さw 3つ目で使ってるので許してネ。 3つ目で firstWinsMerger() の代わりに lastWinsMerger() を用いると、headMap.get("J") の返り値は "JRuby" となります。 throwingMerger() の場合は IllegalStateException が投げられます。

要素をそのままキー or 値に使いたい場合に「s -> s」と書いてますが、JavaDoc ではこの恒等変換を「Functions.identity()」と書いてあるところがあります。 ただし、悲しいことに Functions というクラスが存在しないんだが・・・ 昔のビルドの名残かな。

第4引数の Map の実装を指定する箇所では、コンストラクタの参照「《クラス名》::new」を使うのがよろしいようで。 型パラメータの指定がなくても(付けられないけど)怒られない模様。

partioningBy() メソッド

partitioningBy() メソッドは、ストリームの各要素を「ある命題」に対して true を返すものと false を返すものに分割(グループ分け)するメソッドです。 数学っぽく言えば、「ある命題」に対する真理集合とその補集合に分割すると言ってもいいかと*1。 「ある命題」は java.util.stream.Predicate インターフェースのオブジェクトで表します*2。 Collector の返り値の Map はキーとして Boolean 型の値(つまりは true / false)をとります。 オーバーロードされた2つの partitioningBy() メソッドのシグニチャは以下の通り:

static <T> Collector<T,Map<Boolean,List<T>>>
partitioningBy(Predicate<? super T> predicate)

static <T,D> Collector<T,Map<Boolean,D>>
partitioningBy(Predicate<? super T> predicate,
               Collector<? super T,D> downstream)

2つ目のメソッドの第2引数の Collector は、true (or false) に分類された要素(群)をどのようにマップの値として保持するかを指定します。 この Collector を指定しない1つ目の partitioningBy() メソッドでは、true (or false) に分類された要素をまとめて List オブジェクトに保存し、それをマップの値としています。 ではサンプルコード:

import static java.util.stream.Collectors.*;

// partitioningBy(predicate)
Stream<String> stream3 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");
Map<Boolean,List<String>> jstartMap1 = stream3.collect(partitioningBy(s->s.startsWith("J")));
    // "J" で始まるかどうかで分割
assertStringList(jstartMap1.get(true), "Java", "Jython", "JRuby");
assertStringList(jstartMap1.get(false), "Groovy", "Scala", "Clojure", "Kotlin");

// partitioningBy(prediate, collector)
Stream<String> stream4 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");
Map<Boolean,StringJoiner> jstartMap2 = stream4.collect(partitioningBy(s->s.startsWith("J"), toStringJoiner("/")));
    // "J" で始まる(or 始まらない)文字列を "/" で連結して StringJoiner として保持
assertString(jstartMap2.get(true), "Java/Jython/JRuby");
assertString(jstartMap2.get(false), "Groovy/Scala/Clojure/Kotlin");

/** 第1引数の文字列リストと第2引数の文字列配列をリストにしたものをアサーション */
public static void assertStringList(List<String> list, String... ss){
    assert list.equals(Arrays.asList(ss));
}

/** 第1引数の StringJoiner を toString() で文字列にして、第2引数の String とアサーション */
public static void assertString(StringJoiner sj, String s){
    assertString(sj.toString(), s);
}

前回やった mapping() や reducing() は、マップの値として単なるコレクションではなく、何らかの変換や計算をしたものを使いたい場合に重宝するようです。 まぁ、コレクションでも Set にしたいときには toSet() を使ったりもできるし、その他どんな Collector でも使えますけどね。

groupingBy() メソッド

groupingBy() メソッドは partitionigBy() メソッドと同じようにストリームの要素をグループ分けしますが、partitioningBy() のように true/false の2つに分割するのではなく、もっと多くのグループに分けます。 先ほどの例の「"J" で始まるかどうか」ではなく、「頭文字によってグループ化」という感じになります。 数学で言うと商集合を作るって感じですかね。 商集合を作る同値関係は groupingBy() の第1引数に渡された Function を用いてストリームの各要素を変換した結果得られるオブジェクトの equals() による同値関係・・・でしょうね。 まぁ、そういうのはさておき、groupingBy() のオーバーロードされたシグニチャは3つ:

static <T,K> Collector<T,Map<K,List<T>>>
groupingBy(Function<? super T,? extends K> classifier)

static <T,K,D> Collector<T,Map<K,D>>
groupingBy(Function<? super T,? extends K> classifier,
           Collector<? super T,D> downstream)

static <T,K,D,M extends Map<K,D>> Collector<T,M>
groupingBy(Function<? super T,? extends K> classifier,
           Supplier<M> mapFactory,
           Collector<? super T,D> downstream)

2つ目の第2引数、3つ目の第3引数として渡される Collector は partitioningBy() の Collector と同じく、同一グループに分類された要素(群)をマップの値としてどのように保持するかを指定します。 この Collector を指定する必要のない1つ目の groupingBy() は単なるリストとして要素群を保持します。 3つ目の第2引数に渡される Supplier はマップの実装を指定します。 ではサンプルコード:

import static java.util.stream.Collectors.*;

// groupingBy(classifier)
Stream<String> stream5 = Stream.of(
        "Java", "Groovy", "Scala", "Clojure", "Jython", "JRuby", "Go", "Smalltalk", "C++", "C#");
Map<Character, List<String>> langDic1 = stream5.collect(groupingBy(s -> s.charAt(0)));
    // 頭文字でグループ化
assertStringList(langDic1.get('J'), "Java", "Jython", "JRuby");
assertStringList(langDic1.get('G'), "Groovy", "Go");

// groupingBy(classifier, collector)
Stream<String> stream6 = Stream.of(
        "Java", "Groovy", "Scala", "Clojure", "Jython", "JRuby", "Go", "Smalltalk", "C++", "C#");
Map<Character,StringJoiner> langDic2 = stream6.collect(groupingBy(s->s.charAt(0), toStringJoiner(" ")));
    // 頭文字でグループ化。 グループ内の文字列はスペース " " で連結して StringJoiner として保持
assertString(langDic2.get('S'), "Scala Smalltalk");
assertString(langDic2.get('C'), "Clojure C++ C#");

// groupingBy(classifier, mapFactory, collector)
Stream<String> stream7 = Stream.of(
        "Java", "Groovy", "Scala", "Clojure", "Jython", "JRuby", "Go", "Smalltalk", "C++", "C#");
Map<Character,Set<String>> langDic3 = stream7.collect(groupingBy(s->s.charAt(0), TreeMap::new, toSet()));
    // 頭文字でグループ化。 Map の実装として TreeMap を使用しよう
assert langDic3 instanceof TreeMap;
assert langDic3.get('J').size() == 3;

/** 第1引数の文字列リストと第2引数の文字列配列をリストにしたものをアサーション */
public static void assertStringList(List<String> list, String... ss){
    assert list.equals(Arrays.asList(ss));
}

/** 第1引数 StringJoiner を toString() で文字列にして、第2引数の String とアサーション */
public static void assertString(StringJoiner sj, String s){
    assertString(sj.toString(), s);
}

基本的には partitioningBy() と使い方は変わりませんが、こちらの方がよく使いそう。

さて、これで Collectors クラスに定義されているメソッドは1通り見終わりました。 次回は独自 Collector を作ろうかな。 全く関係ないことやるかも。

オブジェクト指向プログラマが次に読む本 ?Scalaで学ぶ関数脳入門

オブジェクト指向プログラマが次に読む本 ?Scalaで学ぶ関数脳入門

*1:ストリームに含まれている要素すべての集合を全体集合として。

*2:prediate は「述語」の意。 Predicate インターフェースは boolean 値を返す抽象メソッド test() を持ちます。

Java 8 のメソッド参照について

ちょっと頭痛がするので「こなし」記事。

演算子の参照


プリミティブ型の演算子をメソッド参照みたく取得することはデキナイ?

IntBinaryOperator plus = (i, j) -> i + j;

はもちろん OK だけど

IntBinaryOperator plus = int::+;

みたいな風には書けないのかな? まぁ、たとえ書けたとしても最初の方が分かりやすいけど。

メソッド参照があいまいなことがある


Java8 から「::」演算子を使ってメソッドを関数オブジェクトとして参照することができるようになりました:

// インスタンスメソッド String#length() : Integer の参照
Function<String, Integer> func1 = String::length;
assert func1.apply("Java 8") == 6;

// static メソッド String#valueOf(Integer) : String の参照
Function<Integer, String> func2 = String::valueOf;
assert func2.apply(1).equals("1");

2つ目の static メソッドの参照は問題ないと思います。 1つ目のインスタンスメソッドの参照では、(必要なら)apply() メソッドの第1引数(今の場合 String)に対して length() メソッドを探します。 さて、ここで

Function<Integer> f = Integer::toString;

とすると「メソッド参照が無効です。toString() のメソッド参照があいまいです」とコンパイラに怒られてしまいます。 これは

  • Integer のインスタンスメソッド toString() : String
  • Integer の static メソッド String#toString(Integer) : String

の区別がつかないためのよう。 実際に使いたいならラムダ式で

Function<String, Integer> func1 = s -> s.length();
Function<Integer, String> func2 = s -> String.valueOf(s);

のように書かないといけないようです。 まぁ、今後どちらかが優先して返される、なんて変更もありえますが。

蒐集してやんよ java.util.stream.Collectors クラス (1) - Map 以外を返す Collector -

今回から何回かに分けて java.util.stream パッケージに定義されている Collectors クラスに定義されているメソッドを見ていきます。

目次

  1. Map 以外を返す Collector
  2. Map を返す Collector
  3. 内蔵コレクタの特性
  4. parallel ストリーム と concurrent コレクタ
  5. 独自コレクタを作ってみよう!
  6. 独自コレクタを作る必要はない!?

Collector インターフェースと Collectors クラス

Collectors クラスはいわゆるユーティリティ・クラスで、インスタンス生成やクラス拡張はできず、static メソッドのみが定義されています。 これらの static メソッドはほとんどが java.util.stream.Collector オブジェクトを返すメソッドで*1、よく使うであろうコレクターが定義されてます。

Collector インターフェースは Stream インターフェースの collect() メソッドに引数として渡して使います。 Collector インターフェースは型パラメータが2つ宣言されていて

package java.util.stream;

public interface Collector<T,R>{ ... }

T はストリームの要素の型(つまり Stream<T> オブジェクトの collect() メソッドに渡せる)、R は collect() メソッドの返り値の型になります。 今回は返り値が Map でないコレクターを見ていきます:

今回扱うメソッドは以下のもの:

// 汎用コレクター
static <T,U,R> Collector<T,R>
mapping(Function<? super T,? extends U> mapper,
        Collector<? super U,R> downstream)

static <T> Collector<T,T>
reducing(BinaryOperator<T> op)

static <T> Collector<T,T>
reducing(T identity,
         BinaryOperator<T> op)

static <T,U> Collector<T,U>
reducing(U identity,
         Function<? super T,? extends U> mapper,
         BinaryOperator<U> op)

// 文字列への変換
static Collector<String,StringBuilder>
toStringBuilder()

static Collector<CharSequence,StringJoiner>
toStringJoiner(CharSequence delimiter)

// コレクションへの変換
static <T,C extends Collection<T>> Collector<T,C>
toCollection(Supplier<C> collectionFactory)

static <T> Collector<T,List<T>>
toList()

static <T> Collector<T,Set<T>>
toSet()

// 要約統計量 Summary Statistics
static <T> Collector<T,Long>
counting()

static <T> Collector<T,Long>
sumBy(Function<? super T,Long> mapper)

static <T> Collector<T,T>
maxBy(Comparator<? super T> comparator)

static <T> Collector<T,T>
minBy(Comparator<? super T> comparator)

static <T> Collector<T,IntSummaryStatistics>
toIntSummaryStatistics(ToIntFunction<? super T> mapper)

static <T> Collector<T,LongSummaryStatistics>
toLongSummaryStatistics(ToLongFunction<? super T> mapper)

static <T> Collector<T,DoubleSummaryStatistics>
toDoubleSummaryStatistics(ToDoubleFunction<? super T> mapper)

引数の型の型パラメータまで見てると億劫になるので、とりあえず返り値の Collector の第2型パラメータが、Stream#collect() メソッドに Collector を渡した後の返り値だっ、てところをチェックしておけばいいんじゃないかなぁ。

汎用コレクター

まずはある程度汎用的に使える Collector を返すメソッド:

static <T,U,R> Collector<T,R>
mapping(Function<? super T,? extends U> mapper,
        Collector<? super U,R> downstream)

static <T> Collector<T,T>
reducing(BinaryOperator<T> op)

static <T> Collector<T,T>
reducing(T identity,
         BinaryOperator<T> op)

static <T,U> Collector<T,U>
reducing(U identity,
         Function<? super T,? extends U> mapper,
         BinaryOperator<U> op)

Stream インターフェースに定義されている map() や reduce() と何が違うの?という気もしますが、JavaDoc を見ていると、次回やる groupingBy(), partitioningBy() メソッドのうち Collector オブジェクトを引数にとるものに対して使うといいようです。 まぁ、今回はあまり気にせずに普通に使ってみましょう:

import static java.util.stream.Collectors.*;

// mapping() 各文字列を、その長さの int 値に変換
Stream<String> stream0 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");
List<Integer> lengthList = stream0.collect(mapping(s -> s.length(), toList()));
assert lengthList.get(2) == "Scala".length();

// reducing() その1 文字列を連結
Stream<String> stream1 = Stream.of("Java", "Groovy", "Scala", "Clojure");
assertString(stream1.collect(reducing((s0, s1) -> s0 + s1)), "JavaGroovyScalaClojure");

// reducing() その2
Stream<String> stream2 = Stream.of("Java", "Groovy", "Scala", "Clojure");
assertString(stream2.collect(reducing("*", (s0, s1) -> s0 + s1)), "*JavaGroovyScalaClojure");

// reducing() その3
Stream<String> stream3 = Stream.of("Java", "Groovy", "Scala", "Clojure");
assert stream3.collect(reducing(0, s -> s.length(), (i, j) -> i + j)) == 22;

/** 文字列のアサーション */
public static void assertString(String s0, String s1){
    assert s0.equals(s1);
}

reducing() のうち、1つ目、2つ目のものはストリームの要素の型と返り値の型が同じになってます。 また、引数が BinaryOperator<T> オブジェクトを1つだけとる reducing() の返り値は T ですが、Stream<T> インターフェースの reduce() メソッドは Optional<T> であるという違いもあります。

文字列への変換

次は文字列に関するコレクターを返すメソッド:

static Collector<String,StringBuilder>
toStringBuilder()

static Collector<CharSequence,StringJoiner>
toStringJoiner(CharSequence delimiter)

java.util.StringJoiner クラスは Java 8 から導入された新たなクラスで、複数の文字列 (CharSequence) を指定した文字列(デリミター)で連結して返すメソッドです。 使い方はどちらも同じようなものですが、返り値が String オブジェクトではないことに注意:

import static java.util.stream.Collectors.*;

// StringBuilder を構築
Stream<String> stream4 = Stream.of("Java", "Groovy", "Scala", "Clojure");
assertString(stream4.collect(toStringBuilder()).toString(),
             "JavaGroovyScalaClojure");

// StringJoiner を構築
Stream<String> stream5 = Stream.of("Java", "Groovy", "Scala", "Clojure");
assertString(stream5.collect(toStringJoiner(", ")).toString(),
             "Java, Groovy, Scala, Clojure);

/** 文字列のアサーション */
public static void assertString(String s0, String s1){
    assert s0.equals(s1);
}

たぶん StringJoiner を使うコレクターは結構デバッグなので多用することになるかと。

コレクションへの変換

次はストリームをコレクションに変換するコレクター。 List, Set に変換するコレクターと、具象クラスを指定してコレクションを構築するコレクターがあります:

static <T,C extends Collection<T>> Collector<T,C>
toCollection(Supplier<C> collectionFactory)

static <T> Collector<T,List<T>>
toList()

static <T> Collector<T,Set<T>>
toSet()

使い方は一番簡単かと:

import static java.util.stream.Collectors.*;

// Collection の構築 (TreeSet)
Stream<String> stream6 = Stream.of("Java", "Groovy", "Scala", "Clojure");
NavigableSet<String> naviset = stream6.collect(toCollection(TreeSet::new));
assert naviset instanceof NavigableSet;
assertString(naviset.stream().collect(toStringJoiner("")), "ClojureGroovyJavaScala");

// List の構築
Stream<String> stream7 = Stream.of("Java", "Groovy", "Scala", "Clojure");
List<String> list = stream7.collect(toList());
assert list.size() == 4;
assert list instanceof ArrayList;

// Set の構築
Stream<String> stream8 = Stream.of("Java", "Groovy", "Groovy", "Scala", "Java", "Clojure", "Java");
Set<String> set = stream8.collect(toSet());
assert set.size() == 4;
assert set instanceof HashSet;
set.forEach(e -> { assert stream().anyMatch(f -> e.equals(f)); });

/** 文字列のアサーション */
public static void assertString(Object s0, String s1){
    assert s0.toString().equals(s1);
}

まぁ、List と Set で大抵はなんとかなるかな。 toList(), toSet() で生成されるオブジェクトの型をアサーションしてますが、これは実装によるかと。

要約統計量

要約統計量(summary statistics 代表値)は和とか平均とかのことですが、プリミティブ型ストリームの記事で同様のメソッドを扱いましたね。 ここでは同じことを、数値に変換する方法や Comparator オブジェクトとともに指定する感じです。 counting() は単に要素の個数を数えるだけですが:

static <T> Collector<T,Long>
counting()

static <T> Collector<T,Long>
sumBy(Function<? super T,Long> mapper)

static <T> Collector<T,T>
maxBy(Comparator<? super T> comparator)

static <T> Collector<T,T>
minBy(Comparator<? super T> comparator)

static <T> Collector<T,IntSummaryStatistics>
toIntSummaryStatistics(ToIntFunction<? super T> mapper)

static <T> Collector<T,LongSummaryStatistics>
toLongSummaryStatistics(ToLongFunction<? super T> mapper)

static <T> Collector<T,DoubleSummaryStatistics>
toDoubleSummaryStatistics(ToDoubleFunction<? super T> mapper)

使い方もプリミティブ型ストリームのときと同じです:

import static java.util.stream.Collectors.*;

// counting()
Stream<String> stream9 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");
assert stream9.collect(counting()) == 7;

// sumBy()
Stream<String> stream10 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");
assert stream10.collect(sumBy(s -> Long.valueOf(s.length()))) == 39;

// maxBy()
Stream<String> stream11 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");
assertString(stream11.collect(maxBy((s0, s1) -> s0.length() - s1.length())), "Clojure");

// IntSummaryStatistics の構築
Stream<String> stream12 = Stream.of("Java", "Groovy", "Scala", "Clojure", "Kotlin", "Jython", "JRuby");
IntSummaryStatistics stat = stream12.collect(toIntSummaryStatistics(s -> s.length()));
assert stat.getCount() == 7;
assert stat.getSum() == 39;
assertDouble(stat.getAverage(), 39.0/7.0);

次回は返り値が Map のコレクターを見ていきます。

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

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

*1:次回扱う、merger をあらわす BinaryOperator を返すメソッド以外。

小川のせせらぎもやがてはうねる奔流に Stream インターフェース (8) - プリミティブ型ストリーム -

Java の Stream インターフェースに定義されているメソッドを見ていくシリーズ(目次)。 前回までで Stream インターフェースに定義されているメソッドは大体見たので、今回はプリミティブ型のストリーム(以下、プリミティブ型ストリーム)を見ていきます。 プリミティブ型ストリームには

  • IntStream
  • LongStream
  • DoubleStream

(どれも java.util.stream パッケージ)のインターフェースの3つがあります。 遂に全プリミティブ型をサポートするのを諦めた感じでw java.util.function パッケージのインターフェース群も int, long, double の3つのプリミティブ型しかサポートしてしてませんでしたね。 まぁ、boolean とか char とか必要になったら、boxing によってラッパー型扱えばいいだけですけどね。

これらのプリミティブ型ストリームに定義されているメソッドは、ほとんどが Stream インターフェースに定義されているものの焼き直しです。 例えば Stream インターフェースに定義されている以下のメソッドは

<R> Stream<R> map(Function<? super T,? extends R> mapper)
Optional<T> reduce(BinaryOperator<T> accumulator)

IntStream では

IntStream map(IntUnaryOperator mapper)
OptionalInt reduce(IntBinaryOperator op)

という風に型パラメータに int を代入したかのようなメソッドになっています(型としては継承関係などのない完全に別の型ですが):

  • Function<T, R> → IntUnaryOperator*1
  • BinaryOperator<T> → IntBinaryOperator
  • Optional<T> → OptionalInt

これらのメソッドの使い方は Stream インターフェースの場合と同じなので、というか型パラメータがないので型推論機能と合わせて使えばむしろ Stream よりもわかりやすいので、説明は不要かと思います。 よって、プリミティブ型ストリームのメソッドの中で Stream インターフェースに対応するものがないメソッドを見ていけば充分でしょう。 以下では、プリミティブ型ストリームの中でも特に IntStream を見ていきます。

プリミティブ型ストリーム特有のメソッド

プリミティブ型ストリーム、特に int 値のストリームである IntStream には、以下のようなメソッドが定義されています:

package java.util.stream;

public interface IntStream{

    ...    // Stream インターフェースに定義されているのと同様のメソッド

    // static ファクトリメソッド
    static IntStream range(int startInclusive, int endExclusive);
    static IntStream range(int startInclusive, int endExclusive, int step);

    // Map 処理
    Stream<Integer> boxed();
    LongStream longs();
    DoubleStream doubles();
    <U> Stream<U> mapToObj(IntFunction<? extends U> mapper);

    // Reduce 処理
    int sum();
    OptionalDouble average();
    IntSummaryStatistics summaryStatistics();
}
  • static ファクトリメソッド
  • Map 処理
  • Reduce 処理

の3つに分けてそれぞれを見ていきましょう。

static ファクトリメソッド

プリミティブ型ストリームには範囲を表すストリームを生成する range() メソッド が static メソッドとして定義されています。 何気にプリミティブ型ストリームのなかで一番多用するメソッドなんじゃないかと。 range() メソッドには

  • 始値(含む)と終了値(含まない)を指定する
  • 始値(含む)と終了値(含まない)と刻み幅を指定する

という2つのバージョンがあります。 終了値は含まないことに気を付ければ使い方は簡単:

// range()
IntStream is0 = IntStream.range(0, 10);
is0.forEach(System.out::print);    // 「0123456789」 と表示

IntStream is1 = IntStream.range(0, 10, 2);
is1.forEach(System.out::print);    // 「02468」 と表示

IntStream ってこれのためにあるって思ってもいいんじゃないかってくらいかもw

Map 処理

次は Map 処理。 プリミティブ型から別の(プリミティブ型)ストリームを返すメソッドです。

  • boxed() はラッパー型の Stream オブジェクトに
  • longs() は LongStream オブジェクトに
  • doubles() は DoubleStream オブジェクトに
  • mapToObj() は Stream オブジェクトに

それぞれ変換します。 ちなみに LongStream, DoubleStream には IntStream オブジェクトに変換する ints() のようなメソッドは定義されていません*2。 使い方はこんな感じ(ちょっと込み入ったサンプルですが):

// boxed()
assert IntStream.range(0, 10)
                .boxed()    // これがないと次の行で i.toString() が怒られる
                .map(i -> i.toString())
                .collect(Collectors.toStringJoiner("-")).toString()
            .equals("0-1-2-3-4-5-6-7-8-9");

// longs()
assert Integer.MAX_VALUE == 2147483647;

assert IntStream.range(1, 12).reduce(1, (i1, i2) -> i1 * i2) == 39916800;    // 1から12 までの積
assert IntStream.range(1, 13).reduce(1, (i1, i2) -> i1 * i2) == 479001600;
assert IntStream.range(1, 14).reduce(1, (i1, i2) -> i1 * i2) == 1932053504;    // 間違い! 桁落ち

assert IntStream.range(1, 12).longs().reduce(1, (i1, i2) -> i1 * i2) == 39916800L;
assert IntStream.range(1, 13).longs().reduce(1, (i1, i2) -> i1 * i2) == 479001600L;
assert IntStream.range(1, 14).longs().reduce(1, (i1, i2) -> i1 * i2) == 6227020800L;    // 正しい!

// doubles()
assertDouble(IntStream.range(0, 10).map(i -> i/2).sum(), 20);    // 整数の割り算
assertDouble(IntStream.range(0, 10).doubles().map(d -> d/2).sum(), 22.5);    // 小数の割り算

// mapToObj()
String s = "Java 8 Project Lambda";
// 文字列を1文字ごとの Stream に
Stream<String> ss = IntStream.range(0, s.length()).mapToObj(i -> s.substring(i, i+1));
ss.forEach(System.out::println);

/** double 値のアサーション */
public static void assertDouble(double d0, double d1){
    assert Math.abs(d0 - d1) < 0.001;
}

最後の mapToObj() を使うサンプルでは、以前に flatMap() メソッドを使うサンプルで使った文字列を1文字ごとの文字(列)を要素とする Stream オブジェクトへ変換する方法を簡単に書く方法を示しています。

Reduce 処理

最後は Reduce 処理。 数値のストリームなので和や平均を計算するメソッドが定義されています。 ストリームだと、1度要素を列挙してしまうともう戻れないので、Summary statistics (要約統計量、代表値)を一括計算して結果をまとめた java.util.IntSummaryStatistics オブジェクトを返す summaryStatistics() メソッドもあります(LongSummaryStatistics, DoubleSummaryStatistics もありま~す)。 IntSummaryStatistics クラスの定義はこんな感じ:

package java.util;

public Class IntSummaryStatistics implements IntConsumer{

    // Consutructor
    public IntSummaryStatistics()

    // Methods
    void accept(int value);
    void combine(IntSummaryStatistics other);

    long getCount();
    long getSum();
    double getAverage();
    int getMax();
    int getMin();

    String toString();
}

計算できるのは

  • 素数
  • 平均
  • 最大値
  • 最小値

だけです。 せめて分散くらい計算できた方が・・・ まぁともかく、これらを使ったサンプルコードを見てみましょう:

final int n = 100;

// sum()
assert IntStream.range(1, n+1).sum() == n*(n+1)/2;    // 初項1、公差1、項数 n の等差数列の和

// average()
assertDouble(IntStream.range(1, n+1).average().getAsDouble(), (n+1)/2.0);

// summaryStatistics()
IntSummaryStatistics stat = IntStream.range(1, n+1).summaryStatistics();
assert stat.getCount() == n;
assert stat.getSum() == n*(n+1)/2;
assertDouble(stat.getAverage(), (n+1)/2.0);
assert stat.getMin() == 1;
assert stat.getMax() == n;
//println(stat.toString());

/** double 値のアサーション */
public static void assertDouble(double d0, double d1){
    assert Math.abs(d0 - d1) < 0.001;
}
  • IntStream#average() メソッドの返り値は(IntStream でも)OptionalDouble なのに注意。 まぁ、平均は整数とは限らんよね。

Java 8 ea-b90 だと、IntSummaryStatistics#toString() を呼び出すと例外が投げられるんですけど・・・ なんかフォーマット関連の。

プリミティブ型ストリームで一番重要なのはやはり range() メソッドではないかと。 数値だと計算ができるので統計量を取得するメソッドも定義されてますが、かなりしょぼいサポートなので別ライブラリの登場を待つのが吉かと。

これで大体ストリーム関連のインターフェースは終わり。 次は惰性で Collectors のメソッドでも見ていきましょうかね。

プログラミングHaskell

プログラミングHaskell

*1:Function<int, int> みたいな感じ

*2:同様に DoubleStream には longs() も定義されてません。

小川のせせらぎもやがてはうねる奔流に 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 に対応するものかと。

小川のせせらぎもやがてはうねる奔流に 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から始まりますよ。

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

Java の Stream インターフェースに定義されているメソッドを見ていくシリーズ(目次)。 今回は前回に引き続き intermediate operator (中間演算子)を見ていきます。 今回扱うのはストリームの要素はそのままで、その種類を変更する中間演算子です。 ストリームの種類とは逐次 (sequential)並行 (parallel) か、整列されているか (sorted) されていないか (unordered) です。 ただし、整列された Stream オブジェクトを明示的に返す sorted() メソッドは状態を保有する演算子のため、次回に扱います。

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

S sequential()
S parallel()
S unordered()
boolean isParallel()

ここで S は Stream<T> クラスに対しては同じ Stream<T> (のはず)です。 この S は java.util.stream パッケージに定義されているパッケージ・プライベートなインターフェース BaseStream に型パラメータとして指定されています(こちら参照)。 Java8 で言語拡張されたのでないなら、単なるバグでしょうね。 プログラムは普通にコンパイル&実行できますが。

isParallel() メソッドはその Stream オブジェクトが並行かどうかを返すメソッドで、特に説明は必要ないでしょう。 その他のメソッドは Stream オブジェクトを返すので、その後に intermediate operator や terminal operator に対応するメソッドをメソッド・チェーンとしてつなげていけば OK です。

サンプルコード

逐次ストリームと並行ストリームはインターフェース的には違いがないので、逆にどのように処理が違うのか分かりにくいかと思います。 なので違いが分かるんじゃなかろうかというサンプルを書いてみました。 まずは逐次ストリーム(普通のストリーム):

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

stream.peek(s -> sb.append(s+"\n"))
      .map(s -> s.toUpperCase())    // 大文字に変換
      .peek(s -> sb.append("\t"+s+"\n"))
      .map(s -> insertSpace(s))    // 各文字間にスペースを挿入
      .forEach(s -> sb.append("\t\t"+s+"\n"));

System.out.println(sb);

/** 各文字の間にスペースを挿入 */
public static String insertSpace(String s){
    return IntStream.range(0, s.length())
                    .mapToObj(i -> s.substring(i, i+1))
    	            .collect(Collectors.toStringJoiner(" ")).toString();
}

文字列を要素に持つストリームに対して、「大文字に変換 → 各文字の間にスペースを挿入」の順で処理を行っています。 ただし、途中で peek() メソッドによって変換途中の文字列を StringBuffer オブジェクトに格納しています。 これを実行すると、以下のように出力されます:

Java
	JAVA
		J A V A
Groovy
	GROOVY
		G R O O V Y
Scala
	SCALA
		S C A L A
Clojure
	CLOJURE
		C L O J U R E
Kotlin
	KOTLIN
		K O T L I N
Jython
	JYTHON
		J Y T H O N
JRuby
	JRUBY
		J R U B Y

逐次ストリームでは、まず最初の要素の "Java" が最後まで変換され、次に "Groovy" が変換され、・・・という処理が順々に行われます。 全ての文字を大文字に変え、次に全ての要素にスペースを挿入する、という風には実行されていません*1。 さて、次は並行ストリームに対して同様のコードを実行してみます。 コード自体は stream オブジェクトに対する処理の最初の行を

stream.parallel().peek(s -> sb.append(s+"\n"))

のように変えるだけです。 これを実行すると

JRuby
Scala
	SCALA
	JRUBY
Kotlin
	KOTLIN
		S C A L A
		K O T L I N
Clojure
Groovy
	CLOJURE
	GROOVY
		G R O O V Y
		C L O J U R E
Java
	JAVA
Jython
	JYTHON
		J Y T H O N
		J A V A
		J R U B Y

のように、1つの要素(例えば "JRuby")に対する変換(大文字にする、スペースを挿入する)は順々に実行されていますが、どの要素が最初に実行されるかなどの順序は全くランダムに見えます。 もちろん、毎回結果は異なるでしょう。 こういった並行処理を自分で、Thread や java.util.concurrency パッケージのクラス群を使って書くのは結構大変なので、こんな簡単に並行処理が実装できるなんて便利スグル。

まぁ、以上の話で終わりでいいと思いますが、ちょっと各メソッドがどんなインスタンスを返すかを試してみました:

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

// sequential
Stream<String> stream1 = langs.stream();
assert stream1.sequential() == stream1;
assert stream1.parallel()   == stream1;
assert stream1.unordered()  != stream1;

// parallel
Stream<String> stream2 = langs.parallelStream();
assert stream2.sequential() == stream2;
assert stream2.parallel()   == stream2;
assert stream2.unordered()  != stream2;

Set<String> langSet = new HashSet<>(langs);

// unordered
Stream<String> stream3 = langSet.stream();
assert stream3.sequential() == stream3;
assert stream3.parallel()   == stream3;
assert stream3.unordered()  == stream3;

// unordered parallel
Stream<String> stream4 = langSet.parallelStream();
assert stream4.sequential() == stream4;
assert stream4.parallel()   == stream4;
assert stream4.unordered()  == stream4;

// sorted
Stream<String> stream5 = langSet.stream().sorted();
assert stream5.sequential() == stream5;
assert stream5.parallel()   == stream5;
assert stream5.unordered()  != stream5;

結構 parallel() メソッドが自分自身を返してたりするんですね。 単にフラグを保持してて処理を切り替えてる実装なんでしょう。 まぁ、あくまで実装の話ですが。

次回は stateful intermediate operator と short-circuiting stateful intermediate operator の予定。

*1:まぁ、実装に依るかも知れませんが。

Stream インターフェースの親として BaseStream というのがあるようで・・・

Java 8 ea-b90 の話ですが、java.util.stream.Stream インターフェースのスーパーインターフェースとして、java.util.stream.BaseStream というインターフェースがあるようです。 java.util.stream の package private なインターフェースなので直接参照することはできませんが、昔は public だったせいか、java.util.stream パッケージの JavaDoc などに名残(というか修正忘れだろうけどw)がちらほらと。 逆アッセンブルとか逆コンパイルとかはあんまりやり方分からないのでリフレクション使ってちまちまクラス宣言やメソッドを解読(?)してみると、大体以下のような感じのインターフェースのようです:

package java.util.stream;

import java.util.Iterator;
import java.util.Spliterator;

interface BaseStream<T, S extends BaseStream<T, S>>{

    Iterator<T> iterator();
    Spliterator<T> spliterator();

    boolean isParallel();

    S sequential();
    S parallel();
    S unordered();
}

どれもデフォルトメソッドではありません。 seqential(), parallel(), unordered() の返り値が S 型になっていて、これはクラス宣言の第2型パラメータによって指定されています。 Stream インターフェースは、この BaseStream を使って以下のように定義されています(メソッド宣言は省略):

package java.util.stream;

public interface Stream<T> extends BaseStream<T, Stream<T>>{
    ...
}

さて、上記の BaseStream, Stream の定義から、Stream の sequential(), parallel(), unordered() の宣言は

    Stream<T> sequential();
    Stream<T> parallel();
    Stream<T> unordered();

となっているべきですが、JavaDoc (Java 8 ea-b90) を見ると返り値が S 型のままになっとる*1。 JavaDoc で package private の型が public な型の宣言に出てくるなんておかしくない?*2 まさか、Java8 からなんらかの新しい文法が導入されたとか?*3

Java言語仕様 第3版 (The Java Series)

Java言語仕様 第3版 (The Java Series)

*1:Java8 ea-b91 でも同じ。

*2:出てくるように指定はできるけど、そのように指定すると BaseStream インターフェース自体も JavaDoc に出てくるハズ。

*3:Java 5 あたりでメソッドをオーバーライドする際に、メソッドの戻り値がサブタイプでよくなった共変戻り値みたく。

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

Java の Stream インターフェースに定義されているメソッドを見ていくシリーズ(目次)。 今回からは java.util.stream.Stream インターフェースに定義されている intermediate operators を見ていきます。 「intermediate operator」は訳すと「中間演算子」ですかね? Map / Reduce 処理でいう Map 処理を行う演算子で、Stream オブジェクトを別の Stream オブジェクトへ変換します。 今回扱うメソッドは以下のもの:

// filter
Stream<T> filter(Predicate<? super T> predicate)

// map
<R> Stream<R> map(Function<? super T,? extends R> mapper)
IntStream mapToInt(ToIntFunction<? super T> mapper)
LongStream mapToLong(ToLongFunction<? super T> mapper)
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)

// flatMap
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
IntStream flatMapToInt(Function<? super T,? extends IntStream> mapper)
LongStream flatMapToLong(Function<? super T,? extends LongStream> mapper)
DoubleStream flatMapToDouble(Function<? super T,? extends DoubleStream> mapper)

// peak
Stream<T> peek(Consumer<? super T> consumer)

map() と flatMap() にはプリミティブ型(int, long, double)のストリームに変換するメソッドがありますが、使い方は同じなのでここでは割愛。 では、それぞれのメソッドをもう少し詳しく見ていきましょう。

filter() メソッド

filter() メソッドは、ある条件を満たす要素のみを通すフィルターの役割をします。 条件に合わない場合に要素を落とすので、一般に要素数は(同じか)減ります。 フィルターを通す条件は java.util.function.Predicate (述語)オブジェクトによって指定します。 ラムダ式で書く場合は boolean 値を返すようにすれば OK。 使い方はこんな感じ:

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

stream().filter(s -> s.startsWith("J")).forEach(System.out::print);    // 「JavaJythonJRuby」と表示

簡単簡単。

map() メソッド

map() メソッドはストリームの各要素に一定の変換を施して、変換結果のオブジェクトを要素とするストリームを返します。 要素数は変換前後で同じです。

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

stream.map(s -> s.toUpperCase()).forEach(System.out::print);    // 「JAVAGROOVY・・・JRUBY」と表示
//stream.mapToInt(s -> s.length()).forEach(System.out::print);    // 「4657665」と表示

割愛するとか言いつつ、プリミティブ型に変換する mapToInt() を使ったサンプル書いちゃった。

flatMap() メソッド

flatMap() メソッドは、変換前の各要素からそれぞれ Stream オブジェクトを生成し、それらを繋げた Stream を返します。 通常、要素数は(同じか)増えます。

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

stream.flatMap(s -> stringToStream(s)).map(s -> s + " ").forEach(System.out::print);
    // 「J a v a G r o o v y S c a l a ・・・ J R u b y」と表示

/** 文字列を各文字からなる Stream に変換するメソッド。 もっとエレガントに行う方法あるかも。 */
public static Stream<String> stringToStream(String s){
    return IntStream.range(0, s.length()).mapToObj(i -> s.substring(i, i+1));

    /*StreamBuilder<String> builder = Stream.builder();

    for(int i = 0, n = s.length(); i < n; i++)
        builder.add(s.substring(i, i+1));

    return builder.build();*/
}

オブジェクトから Stream を生成するには Stream に定義されている static (ファクトリ)メソッドかコレクションの stream() メソッドを使えばいいかと思います。 配列を Stream にしたい場合は、何気に (static) ユーティリティ・メソッドが追加されている java.util.Arrays クラスを使えば何とかなるかと。

peek() メソッド

peek() メソッドは Stream オブジェクト自体には何も処理を施しませんが、各要素に対して副作用を起こす処理(というか副作用しか起こさない処理)を挟み込むメソッドです。 この副作用は java.util.function.Consumer オブジェクトによって指定します。 ラムダ式で指定する場合は返り値が void であれば OK。 Map / Reduce 処理中に中間状態を表示させたいといったデバッグなどに使うといいようです:

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

stream.peek(s -> System.out.println("[" + s + "]"))    // デバッグのための表示
        .flatMap(s -> stringToStream(s))
        .peek(s -> { assert s.length() == 1;} )    // テスト
        .map(s -> s + " ")
        .peek(s -> { assert s.length() == 2 && s.endsWith(" ");} )    // テスト
        .forEach(System.out::print);

こちらの記事でも書いたように、ラムダ式の中で assert 文を書こうとすると、ブロックにしないといけないもよう(Java 8 ea-b90)。 ちなみに、上記のサンプルを実行すると

[Java]
J a v a [Groovy]
G r o o v y [Scala]
S c a l a [Clojure]
C l o j u r e [Kotlin]
K o t l i n [Jython]
J y t h o n [JRuby]
J R u b y 

のように表示され、Stream オブジェクトに対するメソッド呼び出しが各メソッドごとに行われているわけではないことが分かります。 まぁ、Stream オブジェクト(や JVM の)実装によるんでしょうけど。

今回扱ったメソッドのうち、filter(), map(), flatMap() は大抵の関数型言語で同様のメソッドが定義されてます。 それだけ大事であり王道のメソッドってことですな。 次回は intermediate operator の続きの予定。

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

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

プログラミングClojure 第2版

プログラミングClojure 第2版