2014年1月31日金曜日

Java 8を使ってみる(使用編・Stream編1)

Java 8には、Streamというクラスが追加されています。
面倒くさい説明を少々しますので飛ばす人はこちらへ→スキップ


StreamはC#でいうIEnumbleに準ずるもので、中間処理を実行しても実際の絞り込みは
実行されず、終端処理を呼び出された際に実行されるというものです。

下の例のような記述ができます。

int sumOfRedWeights  = blocks.stream().filter(b -> b.getColor() == RED)
                                           .mapToInt(b -> b.getWeight())
                                           .sum();

ここでは、blocksをgetColor()==REDで絞り込み(filter)、
getWeight()を抽出して(mapToInt)合計しています(sum)。
中間処理のfilter、mapToIntでは絞込は行われず、
終端処理のsumが呼ばれる際に遅延的に実行されます。


言い換えると、繰り返し処理を関数的に記述できるようになるというところでしょうか。
また、終端処理まで状態を保持しないため並列化にも適しています。
stream()のかわりにparallelStream()を使うことによって、処理を並列的に実行します。
サーバではもともとマルチスレッドなので他のコアも処理してたりして、
あまり恩恵ないかもしれないですが、可読性と並列化が捗りますね\(^o^)/。



ということで、
StreamとSQLのクエリと比較してみましょう。


単純なSELECTでTestMember.idのみを抽出する

//SQL
SELECT TestMember.id

//Listに変換
List list = members.stream().map(TestMember::getId)
        .collect(Collectors.toList());


//Setに変換
Set set = members.stream().map(TestMember::getId)
        .collect(Collectors.toCollection(TreeSet::new));


TestMember::getIdは関数渡しでメソッドを渡しています。
map中でgetIdを呼び出した値を利用しています。

 

Member.weightの合計を求める

//SQL
SELECT sum(TestMember.weight)



total = members.stream()
        .mapToInt(x -> x.getWeight())
        .sum();

合計を求める方法には、Collectorを使う方法もあります

int total = members.stream()
        .collect(Collectors.summingInt(x -> x.getWeight()));


平均を求める

//SQL
SELECT avg(TestMember.weight)



double average = members.stream()
        .mapToInt(x -> x.getWeight())
        .average()   
        .orElse(0d);

orElse()というメソッドが出てきました。
average()は、OptionalDoubleというNull許容なオブジェクトを返します。
OptionalDoubleのNullの場合のデフォルト値を指定してdoubleを返すのがorElse()です。
OptionalDoubleは、C#でいうところのdouble?ですね。


切り出し

何番目の要素を指定して取り出すということもできます。


//SQL
SELECT TestMember.weight LIMIT 1

members.stream()
.mapToInt(x -> x.getWeight())
.findFirst()
.orElse(0);


//SQL
SELECT * OFFSET 1 LIMIT 2
members.stream()
.skip(1)
.limit(2).toArray(); 



グループ・パーティション

Stream には group byや partitioned byに当たるメソッドもあります

//グループ分け
Map         = members.stream()
                    .collect(Collectors.groupingBy(x -> x.getPart()));


同じpartのTestMemberをpart毎のマップにする。


本当は、groupingByには関数渡しで、TestMember::getPartとできるはずが、
エラーになっちゃいますね。


//パーティション
Map        members.stream()
                .collect(Collectors.partitioningBy(s -> s.getId() >= 5));




その他

条件に一致したものが一件でもある

boolean match = members.stream().anyMatch(b -> b.getId() > 5);

countを使うより軽いはず?


weightでソートする
ソートをstream、ラムダ式で実装してみた

members.stream().sorted((r,l) -> {
       return Integer.compare(r.weight, l.weight);
}).collect(Collectors.toList());   

ソートするだけならもっと効率のいい方法があるかな
parallelStreamが使えるから早いのか?むむ?



今回使ったプロジェクト->StreamSample.zip

次回もStream編がつづく

0 件のコメント: