試験対策)5回目です。ラムダ式を実際に使っていきます。Java8での追加内容のもう一つの目玉、ストリーム(Stream)に触れていきます。Iterable#forEachメソッドによるコレクションの反復処理コレクションの反復処理は、古くはEnumerationインタフェース、その後Iteratorインタフェース、もしくは普通にインデックスを使ったfor/while文で行っていました。例を書くまでもないですね。Java5でjava.lang.Iterableインタフェース(utilではない)と拡張for文が導入され、シンプルに記述できるようになりました。System.out.println(name);}Java8では、Iterable#forEachというデフォルトメソッドが追加されました。定義は以下の通りです。action);IterableはList,Set,Queue,Dequeといったおなじみのインタフェースが継承しており、これらを実装するサブクラスで利用できます。引数に関数型インタフェースのConsumerを取るので、ラムダ式を渡すことができます。上のfor文は以下のように書けます。System.out.println(name));//さらにこの例では前回説明したメソッド参照を使うと以下のように書けるnames.forEach(System.out::println);キーと値からなるMapインタフェースはIterableのサブインタフェースではありませんが、一貫性のためにMap#forEachメソッドが実装されています。action);引数のBiConsumerインタフェースの部分が分かりづらいですが、引数を2つ、キーと値を取って戻り値voidの処理を渡すことができます。hair));実行結果モモの毛色は白茶トラねねの毛色は三毛すずの毛色は茶トラ反復処理の制御が全て内部で行われ、プログラマは処理の記述に集中できます。ちなみにbreakはどうすればいいのかというと、ラムダ式内でreturnすると次のループに移ります。フィルタリングをしたい場合はこの後説明するストリームAPIを使う方が良いでしょう。ストリームAPIとパイプラインストリームAPIは、コレクション、配列、I/Oリソースなどのデータソースを元に各種の操作を行うAPIです。ストリームはある集計結果をまた次のデータソースとして渡すことができるので、処理をどんどん繋いで求める結果を得ることができます。「文字列の配列に対して、各要素を大文字にし、昇順にソートして出力する」という処理は以下のように書けます。.forEach(System.out::println);Collection#streamメソッドでコレクションからStreamを得ることができます。他にも様々なソースからStreamを得る方法が用意されています。得られたStreamに対し、map,sorted,forEachと次々と操作を呼び出して処理を完了しています。これをストリームのパイプライン処理と呼びます。パイプライン処理には、処理の対象となるデータソースが必要です。データソースからストリームを生成して処理を呼び出しますが、処理には加工やフィルタリングといった、さらに後続の処理を期待する中間操作と、何かしらのアクションを実行したり別のソースへ出力するなどの終端操作があります。Collection#streamかStream#ofだと思います。通常の配列からストリームを生成するArrays#streamもあります。関数型インタフェースと同じように、ストリームにもプリミティブ用のIntStream,LongStream,DoubleStreamが用意されています。オートボクシングによる性能劣化を防ぐため、プリミティブ型を扱う場合は専用のストリームを使いましょう。中間操作ストリームに対し何らかの手を加えた上で新しいストリームを生成する操作です。「手を加えた上で」としてますが実際には即時に実行されるわけではないのですが、それについては後述します。・filter文字通り何かしらの条件によって要素を抽出します。引数にはbooleanを返す関数型インタフェースのPredicateを取ります。!s.startsWith("n"));・distinct重複する要素を取り除きます。取り、戻り値からなる新しいストリームを返します。s.length());Functionなので戻り値の型は何でもよく、異なるデータを持つストリーム(型パラメータが異なる)にすることもできます。型自体は変換できないので、StreamからIntStreamへ変換する場合は専用のメソッドを使います。例に挙げたStream#mapToInt、逆はIntStream#mapToObjといったものがあります。IntStream→Stream<Integer>のようにラッパークラスのストリームへ変換するboxedメソッドなんてのもあります。・flatMap多分とても分かりづらいのがflatMapです。́∀`)flatMapは後の回でもう少し詳しくとりあげますが、簡単に説明しておきます。ポイントはFunctionの2つ目の型パラメータ=戻り値の型がStreamになっているところです。ストリームの各要素に対して何らかの処理をした結果、得られるのがストリーム、つまり複数の値を持つ集合になる場合に使います。そのままだとストリームがたくさんできてしまいますが、flatMapは最終的に得られた全てのStreamを展開して1つのStreamに平坦化してくれます。それがflatと付いている意味です。.forEach(System.out::print);実行結果123456・peekデバッグ用のメソッドです。ストリームの要素に対し引数のConsumerを実行しますが、これは値を返さないので、peek自体の戻りは元のストリームの内容と同じです。action)要素がパイプラインを通過する際にその内容を確認するといった使い方をします。FOURFOUR実行結果がちょっと想像と異なるかもしれません。各中間操作が順番に実行されていくなら、フィルタ後の全要素→マップ後の全要素→最後の出力の順になるところですが、そうなっていませんね。これはストリームAPIの処理は遅延実行※するように作られているためで、受け取ったラムダ式はfilterやmapなどの呼び出し時点では実行されず、何を行うかという情報だけをパイプラインで繋いでいきます。この後説明する終端操作が呼ばれた時に初めて実行されます。上記の例で、forEachをコメントアウトするとpeek内のラムダ式は実行されず、コンソールには何も出力されず終わります。なっているかというと、効率とか性能とか、これはまた別の回で説明する並列ストリーム実現のためで、内部ではかなり複雑なことをやりながら、いい感じに結果を出してくれるというわけです。※遅延評価と書かれていることも多いですが、あまりよろしくないようなので遅延実行としています。・limit,skiplimitは要素を引数の個数に制限、skipは要素の先頭から引数の個数分スキップすします。コードは略!終端操作だいぶ長くなってきましたが、もう少し行きます。前述の通り、終端操作が呼ばれて初めてストリームに対する操作が実行されます。終端操作は、すでに例として出している受け取ったConsumerを実行するだけで何も返さないforEachの他は、Stream以外の何かしらの値を返すものとなっています。・countまず単純なものから。countは要素の個数を返します。count()・max,min引数で渡されたComparatorに従って、最大/最小の要素を返します。comparator);ここで初出となるjava.util.Optionalですが、これはJava8で導入されたクラスで、「nullかもしれない値を格納するコンテナ」です。詳細また別の回に(多いな・・・)。max,minでは、要素が0だった場合に空の(emptyな)Optionalが返されます。・allMatch,anyMatch,noneMatchそれぞれ引数で渡されたPredicateの条件に対し、全て一致/いずれかが一致/全て一致しないかどうかをbooleanで返します。predicate)・findFirst,findAny要素の中で最初の要素、任意の要素を取得します。最初の要素は分かりやすいと思いますが、「任意の要素」ってなんぞ?と思われるでしょう。́∀`)両者の結果が変わってくるのは、まだ説明していない並列ストリームを使用した場合です。.findAny();System.out.println(result);この結果は実行する度に変わり、自分の環境ではOptionalInt[26]かOptionalInt[50]になります。filterを挟んでいるのは処理を入れることでばらつきやすくするためで、filterをコメントアウトしても何度も実行すればばらけます(ほとんど50だがたまに25になりました)。内部でストリームが分割されて並列に処理されているためと思われます。intの要素に対して「50以上のもののうちどれか1つを得る」という状況でfindAnyを使った場合、順次(sequential)ストリームでは最初に見つかった要素(findFirstと同じ)となりますが、並列(parallel)ストリームでは、分割されて並行に処理されたストリームの中で一番最初に見つかったものが返されます。並列の度合いは環境によって異なるので結果は不定になりますが、順序を気にしなくて良いのであれば並列化ストリームを利用しつつfindAnyを使った方が速く結果を得られるということでしょう。・toArrayストリームの要素を配列に変換します。IntFunctionを取る方は、配列コンストラクタ参照を渡すことで簡潔に記述できます。.toArray(Person[]::new);・reduce引数のBinaryOperatorによって要素を集約し、結果を返します。combiner)引数の違いで3つあります。1つめの例は以下の通り。Integer::sum);System.out.println(result);実行結果60最初は初期値と1つめの要素、次はその結果と2つめの要素・・・というふうに順番にBinaryOperatorを呼び出していき、最終的に1つの結果を返します。要素が無い場合は初期値をそのまま返します。引数が1つのものは初期値を与えないバージョンで、要素が無い場合に対応するため戻り値の型がOptional<T>となっています。combinerが追加されています。これは並列ストリームの時に意味を持つものなので、別の回で詳しく触れます。簡単に説明すると、並列ストリームの場合は要素をいくつかに分解して並列にaccumulatorを呼び出していきますが、その結果は最終的に1つにまとめる必要があります。その時にcombinerが使われます。・collect一番ややこしいのがこのcollectです。2種類用意されています。́∀`)引数はjava.util.stream.Collector型です。これは関数型インタフェースではないのでラムダ式は渡せませんが、様々な実装を取得できるjava.util.stream.Collectorsクラスが用意されているのでこれを使います。.collect(Collectors.joining("/"));System.out.println(str);実行結果すず/もも/まお/ねねCollectors#joiningはストリームがStringの時のみ利用可能で、要素の文字列を連結します。オーバーライドされていて3種類あり、例は引数をデリミタとして連結します。長くなってきているので回を分けて説明します。引数3つ取る方ですが、reduceと同じく並列ストリームの場合に生きてくるので、並列ストリームの回に会わせて説明します。【注意】ストリームを使い回してはいけないmapの例であげたコードでは、毎回Stream#ofでストリームを作り直していました。これを無駄だからと、以下のようにすると例外が発生します。test.Main.main(Main.java:14)ストリームの各操作では毎回Streamオブジェクトを返しますが、すでに何かしらの操作を行ったオブジェクトにもう一度操作を行うとIllegalStateExceptionが発生します。メソッドチェーンで文字通り流れるように書いていくのがストリームだと思うので、いちいち変数に入れて・・・とやらなければ問題無いでしょうが、念のため。おしまいのひとこと今回からOracleの公式サイトにあるテスト内容チェックリストと、その英語版の解説記事を元にしてみました。Guide「ラムダ式を使用してコレクションをフィルタリングする」「ストリーム上での遅延操作」は、collectの補足と合わせて次回にします。2016年中の試験代割引キャンペーンには間に合わなそうですが、必ず記事を完遂して試験を受けるので、Java8の新機能に興味のある方はお付き合いいただければと思います。一連の記事は「JavaSE8Gold」ラベルを付けていきます。ホーム▼こちらの記事もどうぞ▼ブログを気に入っていただけたらRSS登録をお願いします!▼ブログランキング参加中!応援よろしくお願いします。にほんブログ村ランキング参加中!スポンサーリンクこのブログを検索スポンサーリンクプロフィール猫とガジェットが好きなJava屋さんです。うつ病で休職後退職し、1年半の休養後に社会復帰。・・・が、いろいろあって再び退職。1年休職の後に復帰して、なんとかSE続けてます。茶トラのすずと一緒に生活していましたが、2014年9月4日に亡くなって1人に。※ヘッダー及びアイコンで使用しているドロイド君は、googleが作成、提供しているコンテンツをベースに複製したものです。▼ブログを気に入っていただけたらRSS登録をお願いします!▼欲しい物リストAmazon欲しい物リスト▼良かったらフォローしてください!(要Googleアカウント?)紹介リンクここから登録していただけると、お互いに特典がもらえます。現在のアイコンはニコ生主で絵描きさんでもあるサトウキビさんに描いていただきました。ヘッダー画像とRSSバナーは山吹たくのさんに描いていただきました。