Upload
hiroto-yamakawa
View
305
Download
19
Embed Size (px)
Citation preview
60分で体験するStream / Lambdaハンズオン
gishi_yama• 某地方大学でeラーニングなどの情報システムの開発・研究に従事
• Wicket-Sapporo, Java Do ← New!
• Wicket User Guideの翻訳にチャレンジ中https://github.com/wicket-sapporo/wicket_user_guide_jp
ハンズオンの概要Java 8のリリースから1年あまりが経過し、Stream APIやLambda 式が開発環境に導入される事例も次第に増えてきていることと思います。
Stream APIやLamda式のしくみ・書き方・ルールの基本と、これらを用いて既存のJavaコードをどう変化させることができるのか、体験してみましょう。
3
練習・演習プロジェクト
https://github.com/java-‐do/20150823_seminar
1. ‘Download Zip’ をして展開(もしくはgitでclone) 2. stream20150823 フォルダをインポート Eclipse:Existing Maven Project(既存のMavenプロジェクト) NetBeans:プロジェクトを開く
3. 指示に従ってHandsOn.javaを編集し、ファイルを実行する 4. 解答例は Answer.java に記載されている
Stream / LambdaJavaに細かい並列処理を導入するため、Java8から導入されたAPIや記法のこと
• Stream:配列、List、Set、Mapといったコレクション的なオブジェクトへの順次・並列処理を記述するAPI
• Lambda:匿名の関数オブジェクトを作るための関数型インターフェースの実装(とその記法)
5
6
ages 22 10 19 38
たとえば、配列を表示する
‣ ages を要素数分繰り返す ‣ iを要素番号として、agesから要素を取り出す ‣ 取り出した要素を表示する
int[] ages = new int[]{22, 10, 19, 38}; for (int i = 0; i < ages.length; i++) { System.out.println(ages[i]); }
7
ages 22 10 19 38
たとえば、配列を表示する
Stream
int[] ages = new int[]{22, 10, 19, 38}; Arrays.stream(ages) .forEach(n -‐> System.out.println(n));
‣ agesをstreamにする ‣ streamの要素nをSystem.out.println(n)で表示する
8
‣ ages を要素数分繰り返す ‣ iを要素番号として、agesから要素を取り出す ‣ 取り出した要素を表示する
‣ agesをstreamにする ‣ streamの要素nをSystem.out.println(n)で表示する
ages 22 10 19 38
たとえば、選んで表示する
‣ ages を要素数分繰り返す ‣ iを要素番号として、agesから要素:nを取り出す ‣ nが20以上の時だけ、下を実行する ‣ nを表示する
※20以上を表示int[] ages = new int[]{22, 10, 19, 38}; for (int i = 0; i < ages.length; i++) { int n = ages[i]; if (n >= 20) { System.out.println(n); } }
10
ages 22 10 19 38
たとえば、選んで表示する
Stream
‣ argsをStreamにする ‣ streamの要素nが20以上のものだけにフィルタする ‣ 残った要素nを表示する
※20以上を表示
int[] ages = new int[]{22, 10, 19, 38}; Arrays.stream(ages) .filter(n -‐> n >= 20) // 22, 38のみのStreamができる .forEach(n -‐> System.out.println(n));
11
‣ ages を要素数分繰り返す ‣ iを要素番号として、agesから要素:nを取り出す ‣ nが20以上の時だけ、下を実行する ‣ nを表示する
‣ argsをStreamにする ‣ streamの要素nが20以上のものだけにフィルタする ‣ 残った要素nを表示する
練習1を やってみましょう
12
13
Streamの構造int[] ages = new int[]{22, 10, 19, 38};
Arrays.stream(ages)
.filter(n -‐> n >= 20)
.forEach(n -‐> System.out.println(n));
‣ 終端操作が実行されるときに、パイプラインの全てが実行される ‣ それぞれにどのような種類があるのかは@hisidamaさんのhttp://www.ne.jp/asahi/hishidama/home/tech/java/stream.html#h_method がわかりやすい
ストリーム・パイプライン
14
ages 22 10 19 38
並列処理
Stream※20以上を表示
int[] ages = new int[]{22, 10, 19, 38}; Arrays.stream(ages) .parallel() .filter(n -‐> n >= 20) .forEach(n -‐> System.out.println(n));
streamをparallel streamに変化させるだけで、並列処理になる ※順序保証だとパフォーマンスは下がる
15
ages 22 10 19 38
要素の入れ替え
Stream
map、mapToXXというメソッドで、要素を入れ替えた Streamを生成し、処理を継続する
Arrays.stream(ages) .filter(n -‐> n >= 20) // 22, 38のみのStreamができる .mapToObj(n -‐> Integer.toBinaryString(n)) // 2進数化したStreamができる .forEach(n -‐> System.out.println(n));
16
Lambda式
・n -‐> n >= 20
・n -‐> Integer.toBinaryString(n)
・n -‐> System.out.println(n)Streamの中に現れた
これらが Lambda(ラムダ)式。
ラムダ式は、”式”の様に処理(関数)を書ける 「関数オブジェクト」
Javaでは、関数型インターフェースの無名クラスが原型
無名クラスインスタンス化の際に{}をつけることで、サブクラスやインターフェースの実装クラスのオブジェクトをその場で作ることができる。このサブクラスには、クラス名がない。 この手法(と、作られたクラス)を無名クラスという。
public class GamePlayer extends AbstractPlayer { // AbstractPlayerのサブクラス // このサブクラスには GamePlayer というクラス名前がある }
AbstractPlayer player = new AbstractPlayer() { // このオブジェクトは、AbstractPlayerのサブクラスのオブジェクトとなる // サブクラスにはクラス名が存在しないので、無名クラスと呼ばれる
};
↓通常のサブクラス↑無名クラス
関数型インターフェース
18
int[] ages = new int[]{22, 10, 19, 38};
IntConsumer consumer = new IntConsumer() { // Consumerの無名クラス
@Override public void accept(int n) { System.out.println(n); } }; Arrays.stream(ages) .forEach(consumer);
例:配列を表示する終端操作の Consumerの無名クラス
int[] ages = new int[]{22, 10, 19, 38};
IntConsumer consumer = new IntConsumer() { // Consumerの無名クラス
@Override public void accept(int n) { System.out.println(n); } }; Arrays.stream(ages) .forEach(consumer);
無名クラスからラムダ式へ
int[] ages = new int[]{22, 10, 19, 38};
IntConsumer consumer = n -‐> System.out.println(n);
Arrays.stream(ages) .forEach(consumer);
ラムダ式
int[] ages = new int[]{22, 10, 19, 38};
IntConsumer consumer = n -‐> System.out.println(n);
Arrays.stream(ages) .forEach(n -‐> System.out.println(n));
引数に直接渡す
アロー演算子の左辺が複数行になるときは{}で囲むint[] ages = new int[]{22, 10, 19, 38};
Arrays.stream(ages) .forEach(n -‐> { String s = n + "歳"; System.out.println(s); });
練習2 演習1~10を やってみましょう
24
List<String> choiced = profiles.stream() .filter(p -‐> p.isFamale()) .filter(p -‐> p.isAdult()) .map(p -‐> p.getName()) .forEach(i -‐> System.out.println(i));
List<String> choiced = profiles.stream() .filter(Profile::isFamale) .filter(Profile::isAdult) .map(Profile::getName) .forEach(System.out::println);
メソッド参照
ラムダ式の部分を オブジェクト::メソッド名 クラス::メソッド名 に書き換えられる(引数も推測される)
副作用は(極力)避けるStringJoiner joiner = new StringJoiner(",", "Profile{", "}"); Stream.of(name, sex.toString(), age.toString(), state, policy) .forEach(joiner::add);
StringJoiner joiner = new StringJoiner(",", "Profile{", "}"); Stream.of(name, sex.toString(), age.toString(), state, policy) .parallel() .forEach(joiner::add);
joiner に変化(副作用)を与えるStream 上(順次処理)と下(並列処理)で結果が異なる! やむをえない場合もあるが、副作用のある処理は極力さけることで、 エラーの防止や並列化をしやすくなる
※なお、ラムダ式の名で呼び出される変数は暗黙的にfinalとなる (上の例では、joinerがfinalとして扱われる)
まとめ
27
まとめStream: コレクション的なオブジェクトへの順次・並列処理をパイプラインで記述できる
Lambda: 関数型インターフェースの実装(Stream内で処理させたい内容など)を式やメソッド参照で書ける
Stream API / Lambdaを使うことで、for文を使う処理をより明確に・簡素に書ける
streamをparallel化するだけで並列処理を行わせることができるようになる(副作用の有無に注意)
Wicketも将来的には…
Martijn Dashorst(2015). Apache Wicket 10 years beyond
参考文献・Javaによる関数型プログラミング(ISBN:4873117046) ・Javaプログラマーなら習得しておきたい Java SE 8 実践プログラミング (ISBN:4844336673)
参考文献・きつねさんと学ぶ Lambda式&StreamAPIハンズオン http://www.slideshare.net/bitter_fox/handson-50579009https://bitbucket.org/bitter_fox/lambda.git
・ひしだまのホームページ:Java Stream http://www.ne.jp/asahi/hishidama/home/tech/java/stream.html
・Java8のstreamを使いこなす:きしだのはてな http://d.hatena.ne.jp/nowokay/20130504
・JavaのStreamで学ぶ遅延処理実装パターン http://www.slideshare.net/mikeneck/javastream
・社内Java8勉強会 ラムダ式とストリームAPI http://www.slideshare.net/zoetrope/java8-lambdaandstream
・Apache Wicket 10 years beyond http://www.slideshare.net/dashorst/wicket-10-years-and-beyond