SlideShare ist ein Scribd-Unternehmen logo
1 von 29
Downloaden Sie, um offline zu lesen
社内勉強会資料
Java 8 Lambdas
輪読会
第3章
Streams
大槌 剛彦 (@ohtsuchi)
1
第3章の内容
• External Iteration と Internal Iteration
• Stream のメソッドの分類: lazy と eager
• Stream のメソッドの使用例: collect , map , filter ,flatMap,
min(max) , reduce
• レガシーコードのリファクタリング例
※本章でも、p3 のDomainクラス (Artist, Track, Album) を使ってサンプルコードが記述されています
2
From External Iteration to Internal Iteration (1)
• (例) ロンドン出身のArtistの数を数える
– この処理に対応した3つの例
• Example 3-1 forループ 使用
• Example 3-2 iterator 使用
• Example 3-3 stream 使用
• Example 3-1 forループ 使用
• いくつかの問題
– 多くの boilerplate code
– parallel version を書くのが難しい
– プログラマの意図が伝わりにくい
– ループ の 本体を読む必要
• 本体が長い場合、特にnestedループの場合、負担
int count = 0;
for (Artist artist : allArtists) {
if (artist.isFrom("London")) {
count++;
}
}
3
From External Iteration to Internal Iteration (2)
• Example 3-2 iterator 使用 -> External Iteration
– 明示的に hasNext と next を呼び出し
• Figure 3-1. External Iteration
• - 異なる振る舞いのoperationの抽象化が難しい
int count = 0;
Iterator<Artist> iterator = allArtists.iterator();
while(iterator.hasNext()) {
Artist artist = iterator.next();
if (artist.isFrom("London")) {
count++;
}
}
4
From External Iteration to Internal Iteration (3)
• Example 3-3 stream 使用 -> Internal Iteration
• Stream とは
– 関数的アプローチで、collection に対して 複雑な operation を構築するツール
• Figure 3-2. Internal Iteration
long count = allArtists.stream()
.filter( artist -> artist.isFrom("London") )
.count(); Stream のメソッド
関数 (戻り値: true or false)
5
From External Iteration to Internal Iteration (4)
• Example 3-3 では 2つのシンプルなoperation に分解
– London 出身の全ての artist を見つける
– そのartistのlistの数を数える
→ 2つの operation 共に Stream インタフェースで対応
– filter()
• test に pass した object のみを残す
– test は 関数で定義
» true or false で返す
– count()
• Stream の中の object の数を数える
6
What’s Actually Going On (1)
• Stream のメソッド
– builder pattern に似ている
• プロパティを設定する一連のメソッドの呼び出し
• -> 最後に build メソッドが呼び出されて初めてobjectが作成される
– 多くの異なる operation に対して、1回の iterate で済ます
• Lazy
– 例) filter
– 戻り値: Stream
• builds up a Stream recipe
– メソッドチェーン
• eager
– 例) count
– 戻り値: 値 または void
– 終端メソッド
7
What’s Actually Going On (2)
• Example 3-4 Just the filter, no collect step
• Example 3-5 Just the filter, no collect step
• Example 3-6 Printing out artist names
allArtists.stream()
.filter( artist -> artist.isFrom("London") );
allArtists.stream()
.filter(artist -> {
System.out.println(artist.getName());
return artist.isFrom("London");
});
long count = allArtists.stream()
.filter(artist -> {
System.out.println(artist.getName());
return artist.isFrom("London");
})
.count();
printlnが実行されない
printlnが実行される
8
Common Stream Operations
• Stream のメソッドの使用例:
– collect
– map
– filter
– flatMap
– min(または max)
– reduce
• A Common Pattern Appears
• Putting Operations Together
9
collect(toList())
• Stream から 値のListを生成
List<String> collected = Stream.of("a", "b", "c")
.collect(Collectors.toList());
assertEquals(Arrays.asList("a", "b", "c"), collected);
10
Stream を生成するファクトリメソッド
map (1)
• Stream内の値を、別の値に変換
• 変換後の値で、別のStreamを生成
• Example 3-8 文字列を大文字に変換: for ループ使用
List<String> collected = new ArrayList<>();
for (String string : Arrays.asList("a", "b", "hello")) {
String uppercaseString = string.toUpperCase();
collected.add(uppercaseString);
}
assertEquals(Arrays.asList("A", "B", "HELLO"), collected);
11
map (2)
• Example 3-9 文字列を大文字に変換: map使用
• Figure 3-4. The Function interface
– 引数が1個(T)、戻り値が1個(R) のFunctionインタフェースをlamda式でmapの引数に指定
(この例では、引数:String, 戻り値:String で同じ型であったが、別の型になってもよい)
List<String> collected = Stream.of("a", "b", "hello")
.map(string -> string.toUpperCase())
.collect(Collectors.toList());
assertEquals(Arrays.asList("A", "B", "HELLO"), collected);
12
filter
• Example 3-10 数字から始まる文字を検索: forループ と if文 使用
• Example 3-11 数字から始まる文字を検索: 「Functional style」 filter使用
– if文と同じ働きをする1個のfunction を lamda式でfilterの引数に指定
– 戻り値: true OR false
– Figure 3-6. The Predicate interface
List<String> beginningWithNumbers = new ArrayList<>();
for(String value : Arrays.asList("a", "1abc", "abc1")) {
if ( Character.isDigit(value.charAt(0)) ) {
beginningWithNumbers.add(value);
}
}
assertEquals(Arrays.asList("1abc"), beginningWithNumbers);
List<String> beginningWithNumbers
= Stream.of( "a", "1abc", "abc1" )
.filter( value -> Character.isDigit(value.charAt(0)) )
.collect( Collectors.toList() );
assertEquals(Arrays.asList("1abc"), beginningWithNumbers);
13
flatMap
• 値 を Stream に変換
• 変換後の全てのStreamを連結
• Example 3-12. Stream list
• mapとの共通点:
– Function を引数にとる
• mapとの違い:
– Function の戻り値(R) は Stream に制限される
List<Integer> together = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4) )
.flatMap(numbers -> numbers.stream())
.collect(Collectors.toList());
assertEquals(Arrays.asList(1, 2, 3, 4), together);
(Map) a value -> a new value
(flatMap) a value -> a new Stream
List<Integer>
-> Stream<Integer>
に変換
(Collection#stream メ
ソッドを使って )
14
max and min
• Example 3-13. 長さが一番短い曲を検索: min 使用
• min や max を考えるのに、 「順序」 を考える必要がある
– 「順序」 をStreamに伝える → Comparator を使用
• この例では、順序に track.getLength を使用
• java8 で Comparator クラスに comparing という static メソッドが追加された
– 引数も戻り値もfunction
• 引数: Function
• 戻り値: Comparator
• min, max の戻り値: Optional (-> 第4章参照)
– get メソッドで値を取得
List<Track> tracks = Arrays.asList(new Track("Bakai", 524),
new Track("Violets for Your Furs", 378),
new Track("Time Was", 451));
Track shortestTrack = tracks.stream()
.min( Comparator.comparing( track -> track.getLength() ) )
.get();
assertEquals(tracks.get(1), shortestTrack);
15
A Common Pattern Appears
• Example 3-14. 長さが一番短い曲を検索: for ループ使用 (Example 3-13を書き換え)
– 変数(shortestTrack )を初期化
– for ループで if文
• If( 変数 と currnetの要素を比較。currentの要素の方が短ければ ) { 変数を書き換え }
– 最終的に 変数(shortestTrack )に一番短い曲が格納されている
• Example 3-15. The reduce pattern (擬似コード)
– If文の代わりに、combine 関数を呼び出し
• 一番短い曲を求める場合は combine は accumulator とcurrnetの要素を比較 して、短い方を返す
• この一般的なパターンが Streams API の operation で体系化されている
Track shortestTrack = tracks.get(0);
for (Track track : tracks) {
if (track.getLength() < shortestTrack.getLength()) {
shortestTrack = track;
}
}
Object accumulator = initialValue;
for(Object element : collection) {
accumulator = combine(accumulator, element);
}
16
reduce (1)
• collection の複数の値から、1つの結果 を生成
– count, min, max, sum, …
• Example 3-16. 要素の足し算. reduceを使用
• 変数 acc が accumulator の役割で 足し算の結果を保持
• 前回までの acc の値と、current の要素を足した結果が戻り値 -> 新しい acc
int count = Stream.of(1, 2, 3)
.reduce( 0, (acc, element) -> acc + element );
assertEquals(6, count);
T, T TBinaryOperator初期値
17
reduce (2)
• Example 3-18. 要素の足し算. Imperative implementation
– 変数の全ての更新を手動で管理
int acc = 0;
for (Integer element : Arrays.asList(1, 2, 3)) {
acc = acc + element;
}
assertEquals(6, acc);
18
Putting Operations Together
• 課題を、シンプルなStreamのoperationに分解
– (例)あるアルバムの中の band の国籍一覧を求める
• アルバムの中のミュージシャン一覧から
• 名前が "The" から始まるミュージシャンを band として扱い
• その band の国籍を求め、
• 国籍一覧を返す
• この domain クラス(Album) には 戻り値がStream のメソッドがあるが、それが無いクラスの場合
– List or Set でOK。 List or Set (Collection) に stream メソッドがあるため。
• domain クラスをカプセル化するのに…
– 戻り値がStream のメソッドを公開するほうが better
• ↑ List or Setが戻り値のメソッドを公開するよりも
• 内部の List or Set に影響を与えないため
Set<String> origins = album.getMusicians()
.filter( artist -> artist.getName().startsWith("The") )
.map( artist -> artist.getNationality() )
.collect( Collectors.toSet() );
戻り値: Stream (lazy)
Artist -> String に変換して
新しい Stream を返す
19
Refactoring Legacy Code (Legacy code)
• forループをStreamに変更:
– step別にリファクタリングする例
• Legacy code : forループ
↓
• step 1: Stream#forEach を使用
↓
• step 2: 内側の forEachブロックを分解
↓
• step 3: ネストを解消
↓
• step 4: 結果保持用の変数を削除
– (例) アルバム一覧から、1分より長い曲の曲名を求める
• Example 3-19. Legacy code
public Set<String> findLongTracks(List<Album> albums) {
Set<String> trackNames = new HashSet<>();
for(Album album : albums) {
for (Track track : album.getTrackList()) {
if (track.getLength() > 60) {
String name = track.getName();
trackNames.add(name);
}
}
}
return trackNames;
} 20
Refactoring Legacy Code (step 1)
• Example 3-20. Refactor step 1: forループ から Stream#forEach 使用に変更
– 上記コードを洗練させるためのターゲット → 内側の forEachブロック
• 3つの処理 に分解 → (step 2) 3つの Stream の operation を使用
– 1分より長い曲を探す → filter
– その曲の名前を取得。曲から曲名に変換 → map
– その曲の名前をSetに格納 → 次のstep ではまだ forEach を使用
public Set<String> findLongTracks(List<Album> albums) {
Set<String> trackNames = new HashSet<>();
albums.stream()
.forEach( album -> {
album.getTracks()
.forEach( track -> {
if (track.getLength() > 60) {
String name = track.getName();
trackNames.add(name);
}
});
});
return trackNames;
}
21
Refactoring Legacy Code (step 2)
• Example 3-21. Refactor step 2: 内側の forEachブロックを分解
– ネストを解消したい
• album → track の stream に変換 → (step3) flatMap 使用
public Set<String> findLongTracks(List<Album> albums) {
Set<String> trackNames = new HashSet<>();
albums.stream()
.forEach(album -> {
album.getTracks()
.filter( track -> track.getLength() > 60 )
.map( track -> track.getName() )
.forEach( name -> trackNames.add(name) );
});
return trackNames;
}
22
Refactoring Legacy Code (step 3)
• Example 3-22. Refactor step 3: ネストを解消
– 自分でSetをnewして、各elementをaddしている
• → (step 4) collect(toSet()) を使用
– 変数 trackNames を消すことができる
public Set<String> findLongTracks(List<Album> albums) {
Set<String> trackNames = new HashSet<>();
albums.stream()
.flatMap( album -> album.getTracks() )
.filter( track -> track.getLength() > 60 )
.map( track -> track.getName() )
.forEach( name -> trackNames.add(name) );
return trackNames;
}
23
Refactoring Legacy Code (step 4)
• Example 3-23. Refactor step 4: 結果保持用の変数を削除
• まとめ
– 段階的にリファクタリング
– 各step毎にユニットテストし続けて確認
public Set<String> findLongTracks(List<Album> albums) {
return albums.stream()
.flatMap( album -> album.getTracks() )
.filter( track -> track.getLength() > 60 )
.map( track -> track.getName() )
.collect( Collectors.toSet() );
}
24
Multiple Stream Calls
• Example 3-24. Stream の誤用
• Example 3-25. Idiomatically chained stream calls
• Example 3-24がExample 3-25より悪い理由
– ビジネスロジックに対して boilerplate code の割合が悪くて読みづらい
– 各中間処理でcollectionオブジェクトを作成しているので非効率
– 各中間処理でしか必要としないゴミ変数がメソッドを汚くする
– 自動並列化が難しくなる
List<Artist> musicians = album.getMusicians()
.collect( Collectors.toList() );
List<Artist> bands = musicians.stream()
.filter( artist -> artist.getName().startsWith("The") )
.collect( Collectors.toList());
Set<String> origins = bands.stream()
.map( artist -> artist.getNationality() )
.collect( Collectors.toSet() );
Set<String> origins = album.getMusicians()
.filter( artist -> artist.getName().startsWith("The") )
.map( artist -> artist.getNationality() )
.collect( Collectors.toSet() );
25
Higher-Order Functions
• 高階関数
– 他の function を引数や戻り値に出来る関数
– シグニチャを見た時に、functional interface が引数や戻り値に使われていたら、そのメ
ソッドは高階関数。
– 例えば mapメソッドは 引数に function を取るので高階関数。
– Streamのほとんど全ての関数は高階関数
– 前に出てきたソートの例では、comparing 関数は、他の関数を引数に取るだけでなく、
戻り値としてComparatorをnewして返していた
– Comparator は object と思うかもしれないが、ただ1つの抽象関数を持っている。
• → functional interface
26
Good Use of Lambda Expressions (1)
• この章で紹介したコンセプト
– よりシンプルなコードを書く
– operation をデータに記述している
• what (何を変換) > how (どのように変換)
– バグが発生する可能性を低くするコード
– プログラマの意図が分かるコード
– 副作用の無い関数
» 何の値 (what values) を返しているのか見るだけで、
» その関数が何をして いるのか (what the functions are doing)
» が理解できる
27
Good Use of Lambda Expressions (2)
– 副作用の無い関数
• 外側から状態を変化させない
• 本書の最初のラムダの例では副作用のある例も載っている
– コンソールに print out
» 観察できる副作用
• では次の例では?
– 変数への代入、という副作用を作り出している
– プログラムの出力からは見えないかもしれないが、
» プログラムの状態を変更している
private ActionEvent lastEvent;
private void registerHandler() {
button.addActionListener( (ActionEvent event) -> {
this.lastEvent = event;
} );
}
28
Good Use of Lambda Expressions (3)
– この例ではローカル変数に代入しようとしている
• → 実際はコンパイルできない
• capture values > capturing variables
– ラムダ式で使用する場合、ローカル変数(上記例ではlocalEvent)に final キーワードが必要ない
• けれども、実質的には final (第2章)
• ラムダ式を Stream の高階関数に渡す時はいつも、副作用が無いことを目指すべき
– 唯一の例外: forEach メソッド
• Internal iteration は collection 側に iterate する処理を委譲して、collection を iterate する手段
• Stream は Internal iteration
• collection の多くの共通 operation は、ラムダ式を使って、Stream のメソッドを組み合わせる事に
よって実行される
ActionEvent localEvent = null;
button.addActionListener(event -> {
localEvent = event;
});
Key Points
29

Weitere ähnliche Inhalte

Ähnlich wie Java8 lambdas chap03

Java8 Lambda chapter5
Java8 Lambda chapter5Java8 Lambda chapter5
Java8 Lambda chapter5Takinami Kei
 
Java SE 8 lambdaで変わる プログラミングスタイル
Java SE 8 lambdaで変わる プログラミングスタイルJava SE 8 lambdaで変わる プログラミングスタイル
Java SE 8 lambdaで変わる プログラミングスタイルなおき きしだ
 
PostgreSQLの実行計画を読み解こう(OSC2015 Spring/Tokyo)
PostgreSQLの実行計画を読み解こう(OSC2015 Spring/Tokyo)PostgreSQLの実行計画を読み解こう(OSC2015 Spring/Tokyo)
PostgreSQLの実行計画を読み解こう(OSC2015 Spring/Tokyo)Satoshi Yamada
 
A Multiple Pairs Shortest Path Algorithm 解説
A Multiple Pairs Shortest Path Algorithm 解説A Multiple Pairs Shortest Path Algorithm 解説
A Multiple Pairs Shortest Path Algorithm 解説Osamu Masutani
 
PostgreSQL - C言語によるユーザ定義関数の作り方
PostgreSQL - C言語によるユーザ定義関数の作り方PostgreSQL - C言語によるユーザ定義関数の作り方
PostgreSQL - C言語によるユーザ定義関数の作り方Satoshi Nagayasu
 
第四回ネットワークチーム講座資料
第四回ネットワークチーム講座資料第四回ネットワークチーム講座資料
第四回ネットワークチーム講座資料densan_teacher
 
「plyrパッケージで君も前処理スタ☆」改め「plyrパッケージ徹底入門」
「plyrパッケージで君も前処理スタ☆」改め「plyrパッケージ徹底入門」「plyrパッケージで君も前処理スタ☆」改め「plyrパッケージ徹底入門」
「plyrパッケージで君も前処理スタ☆」改め「plyrパッケージ徹底入門」Nagi Teramo
 
Pgunconf14 pg13-psql
Pgunconf14 pg13-psqlPgunconf14 pg13-psql
Pgunconf14 pg13-psqlToshi Harada
 

Ähnlich wie Java8 lambdas chap03 (9)

Java8 Lambda chapter5
Java8 Lambda chapter5Java8 Lambda chapter5
Java8 Lambda chapter5
 
Java SE 8 lambdaで変わる プログラミングスタイル
Java SE 8 lambdaで変わる プログラミングスタイルJava SE 8 lambdaで変わる プログラミングスタイル
Java SE 8 lambdaで変わる プログラミングスタイル
 
PostgreSQLの実行計画を読み解こう(OSC2015 Spring/Tokyo)
PostgreSQLの実行計画を読み解こう(OSC2015 Spring/Tokyo)PostgreSQLの実行計画を読み解こう(OSC2015 Spring/Tokyo)
PostgreSQLの実行計画を読み解こう(OSC2015 Spring/Tokyo)
 
A Multiple Pairs Shortest Path Algorithm 解説
A Multiple Pairs Shortest Path Algorithm 解説A Multiple Pairs Shortest Path Algorithm 解説
A Multiple Pairs Shortest Path Algorithm 解説
 
PostgreSQL - C言語によるユーザ定義関数の作り方
PostgreSQL - C言語によるユーザ定義関数の作り方PostgreSQL - C言語によるユーザ定義関数の作り方
PostgreSQL - C言語によるユーザ定義関数の作り方
 
第四回ネットワークチーム講座資料
第四回ネットワークチーム講座資料第四回ネットワークチーム講座資料
第四回ネットワークチーム講座資料
 
「plyrパッケージで君も前処理スタ☆」改め「plyrパッケージ徹底入門」
「plyrパッケージで君も前処理スタ☆」改め「plyrパッケージ徹底入門」「plyrパッケージで君も前処理スタ☆」改め「plyrパッケージ徹底入門」
「plyrパッケージで君も前処理スタ☆」改め「plyrパッケージ徹底入門」
 
ALPSチュートリアル(3) アプリケーション実習
ALPSチュートリアル(3) アプリケーション実習ALPSチュートリアル(3) アプリケーション実習
ALPSチュートリアル(3) アプリケーション実習
 
Pgunconf14 pg13-psql
Pgunconf14 pg13-psqlPgunconf14 pg13-psql
Pgunconf14 pg13-psql
 

Java8 lambdas chap03

  • 2. 第3章の内容 • External Iteration と Internal Iteration • Stream のメソッドの分類: lazy と eager • Stream のメソッドの使用例: collect , map , filter ,flatMap, min(max) , reduce • レガシーコードのリファクタリング例 ※本章でも、p3 のDomainクラス (Artist, Track, Album) を使ってサンプルコードが記述されています 2
  • 3. From External Iteration to Internal Iteration (1) • (例) ロンドン出身のArtistの数を数える – この処理に対応した3つの例 • Example 3-1 forループ 使用 • Example 3-2 iterator 使用 • Example 3-3 stream 使用 • Example 3-1 forループ 使用 • いくつかの問題 – 多くの boilerplate code – parallel version を書くのが難しい – プログラマの意図が伝わりにくい – ループ の 本体を読む必要 • 本体が長い場合、特にnestedループの場合、負担 int count = 0; for (Artist artist : allArtists) { if (artist.isFrom("London")) { count++; } } 3
  • 4. From External Iteration to Internal Iteration (2) • Example 3-2 iterator 使用 -> External Iteration – 明示的に hasNext と next を呼び出し • Figure 3-1. External Iteration • - 異なる振る舞いのoperationの抽象化が難しい int count = 0; Iterator<Artist> iterator = allArtists.iterator(); while(iterator.hasNext()) { Artist artist = iterator.next(); if (artist.isFrom("London")) { count++; } } 4
  • 5. From External Iteration to Internal Iteration (3) • Example 3-3 stream 使用 -> Internal Iteration • Stream とは – 関数的アプローチで、collection に対して 複雑な operation を構築するツール • Figure 3-2. Internal Iteration long count = allArtists.stream() .filter( artist -> artist.isFrom("London") ) .count(); Stream のメソッド 関数 (戻り値: true or false) 5
  • 6. From External Iteration to Internal Iteration (4) • Example 3-3 では 2つのシンプルなoperation に分解 – London 出身の全ての artist を見つける – そのartistのlistの数を数える → 2つの operation 共に Stream インタフェースで対応 – filter() • test に pass した object のみを残す – test は 関数で定義 » true or false で返す – count() • Stream の中の object の数を数える 6
  • 7. What’s Actually Going On (1) • Stream のメソッド – builder pattern に似ている • プロパティを設定する一連のメソッドの呼び出し • -> 最後に build メソッドが呼び出されて初めてobjectが作成される – 多くの異なる operation に対して、1回の iterate で済ます • Lazy – 例) filter – 戻り値: Stream • builds up a Stream recipe – メソッドチェーン • eager – 例) count – 戻り値: 値 または void – 終端メソッド 7
  • 8. What’s Actually Going On (2) • Example 3-4 Just the filter, no collect step • Example 3-5 Just the filter, no collect step • Example 3-6 Printing out artist names allArtists.stream() .filter( artist -> artist.isFrom("London") ); allArtists.stream() .filter(artist -> { System.out.println(artist.getName()); return artist.isFrom("London"); }); long count = allArtists.stream() .filter(artist -> { System.out.println(artist.getName()); return artist.isFrom("London"); }) .count(); printlnが実行されない printlnが実行される 8
  • 9. Common Stream Operations • Stream のメソッドの使用例: – collect – map – filter – flatMap – min(または max) – reduce • A Common Pattern Appears • Putting Operations Together 9
  • 10. collect(toList()) • Stream から 値のListを生成 List<String> collected = Stream.of("a", "b", "c") .collect(Collectors.toList()); assertEquals(Arrays.asList("a", "b", "c"), collected); 10 Stream を生成するファクトリメソッド
  • 11. map (1) • Stream内の値を、別の値に変換 • 変換後の値で、別のStreamを生成 • Example 3-8 文字列を大文字に変換: for ループ使用 List<String> collected = new ArrayList<>(); for (String string : Arrays.asList("a", "b", "hello")) { String uppercaseString = string.toUpperCase(); collected.add(uppercaseString); } assertEquals(Arrays.asList("A", "B", "HELLO"), collected); 11
  • 12. map (2) • Example 3-9 文字列を大文字に変換: map使用 • Figure 3-4. The Function interface – 引数が1個(T)、戻り値が1個(R) のFunctionインタフェースをlamda式でmapの引数に指定 (この例では、引数:String, 戻り値:String で同じ型であったが、別の型になってもよい) List<String> collected = Stream.of("a", "b", "hello") .map(string -> string.toUpperCase()) .collect(Collectors.toList()); assertEquals(Arrays.asList("A", "B", "HELLO"), collected); 12
  • 13. filter • Example 3-10 数字から始まる文字を検索: forループ と if文 使用 • Example 3-11 数字から始まる文字を検索: 「Functional style」 filter使用 – if文と同じ働きをする1個のfunction を lamda式でfilterの引数に指定 – 戻り値: true OR false – Figure 3-6. The Predicate interface List<String> beginningWithNumbers = new ArrayList<>(); for(String value : Arrays.asList("a", "1abc", "abc1")) { if ( Character.isDigit(value.charAt(0)) ) { beginningWithNumbers.add(value); } } assertEquals(Arrays.asList("1abc"), beginningWithNumbers); List<String> beginningWithNumbers = Stream.of( "a", "1abc", "abc1" ) .filter( value -> Character.isDigit(value.charAt(0)) ) .collect( Collectors.toList() ); assertEquals(Arrays.asList("1abc"), beginningWithNumbers); 13
  • 14. flatMap • 値 を Stream に変換 • 変換後の全てのStreamを連結 • Example 3-12. Stream list • mapとの共通点: – Function を引数にとる • mapとの違い: – Function の戻り値(R) は Stream に制限される List<Integer> together = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4) ) .flatMap(numbers -> numbers.stream()) .collect(Collectors.toList()); assertEquals(Arrays.asList(1, 2, 3, 4), together); (Map) a value -> a new value (flatMap) a value -> a new Stream List<Integer> -> Stream<Integer> に変換 (Collection#stream メ ソッドを使って ) 14
  • 15. max and min • Example 3-13. 長さが一番短い曲を検索: min 使用 • min や max を考えるのに、 「順序」 を考える必要がある – 「順序」 をStreamに伝える → Comparator を使用 • この例では、順序に track.getLength を使用 • java8 で Comparator クラスに comparing という static メソッドが追加された – 引数も戻り値もfunction • 引数: Function • 戻り値: Comparator • min, max の戻り値: Optional (-> 第4章参照) – get メソッドで値を取得 List<Track> tracks = Arrays.asList(new Track("Bakai", 524), new Track("Violets for Your Furs", 378), new Track("Time Was", 451)); Track shortestTrack = tracks.stream() .min( Comparator.comparing( track -> track.getLength() ) ) .get(); assertEquals(tracks.get(1), shortestTrack); 15
  • 16. A Common Pattern Appears • Example 3-14. 長さが一番短い曲を検索: for ループ使用 (Example 3-13を書き換え) – 変数(shortestTrack )を初期化 – for ループで if文 • If( 変数 と currnetの要素を比較。currentの要素の方が短ければ ) { 変数を書き換え } – 最終的に 変数(shortestTrack )に一番短い曲が格納されている • Example 3-15. The reduce pattern (擬似コード) – If文の代わりに、combine 関数を呼び出し • 一番短い曲を求める場合は combine は accumulator とcurrnetの要素を比較 して、短い方を返す • この一般的なパターンが Streams API の operation で体系化されている Track shortestTrack = tracks.get(0); for (Track track : tracks) { if (track.getLength() < shortestTrack.getLength()) { shortestTrack = track; } } Object accumulator = initialValue; for(Object element : collection) { accumulator = combine(accumulator, element); } 16
  • 17. reduce (1) • collection の複数の値から、1つの結果 を生成 – count, min, max, sum, … • Example 3-16. 要素の足し算. reduceを使用 • 変数 acc が accumulator の役割で 足し算の結果を保持 • 前回までの acc の値と、current の要素を足した結果が戻り値 -> 新しい acc int count = Stream.of(1, 2, 3) .reduce( 0, (acc, element) -> acc + element ); assertEquals(6, count); T, T TBinaryOperator初期値 17
  • 18. reduce (2) • Example 3-18. 要素の足し算. Imperative implementation – 変数の全ての更新を手動で管理 int acc = 0; for (Integer element : Arrays.asList(1, 2, 3)) { acc = acc + element; } assertEquals(6, acc); 18
  • 19. Putting Operations Together • 課題を、シンプルなStreamのoperationに分解 – (例)あるアルバムの中の band の国籍一覧を求める • アルバムの中のミュージシャン一覧から • 名前が "The" から始まるミュージシャンを band として扱い • その band の国籍を求め、 • 国籍一覧を返す • この domain クラス(Album) には 戻り値がStream のメソッドがあるが、それが無いクラスの場合 – List or Set でOK。 List or Set (Collection) に stream メソッドがあるため。 • domain クラスをカプセル化するのに… – 戻り値がStream のメソッドを公開するほうが better • ↑ List or Setが戻り値のメソッドを公開するよりも • 内部の List or Set に影響を与えないため Set<String> origins = album.getMusicians() .filter( artist -> artist.getName().startsWith("The") ) .map( artist -> artist.getNationality() ) .collect( Collectors.toSet() ); 戻り値: Stream (lazy) Artist -> String に変換して 新しい Stream を返す 19
  • 20. Refactoring Legacy Code (Legacy code) • forループをStreamに変更: – step別にリファクタリングする例 • Legacy code : forループ ↓ • step 1: Stream#forEach を使用 ↓ • step 2: 内側の forEachブロックを分解 ↓ • step 3: ネストを解消 ↓ • step 4: 結果保持用の変数を削除 – (例) アルバム一覧から、1分より長い曲の曲名を求める • Example 3-19. Legacy code public Set<String> findLongTracks(List<Album> albums) { Set<String> trackNames = new HashSet<>(); for(Album album : albums) { for (Track track : album.getTrackList()) { if (track.getLength() > 60) { String name = track.getName(); trackNames.add(name); } } } return trackNames; } 20
  • 21. Refactoring Legacy Code (step 1) • Example 3-20. Refactor step 1: forループ から Stream#forEach 使用に変更 – 上記コードを洗練させるためのターゲット → 内側の forEachブロック • 3つの処理 に分解 → (step 2) 3つの Stream の operation を使用 – 1分より長い曲を探す → filter – その曲の名前を取得。曲から曲名に変換 → map – その曲の名前をSetに格納 → 次のstep ではまだ forEach を使用 public Set<String> findLongTracks(List<Album> albums) { Set<String> trackNames = new HashSet<>(); albums.stream() .forEach( album -> { album.getTracks() .forEach( track -> { if (track.getLength() > 60) { String name = track.getName(); trackNames.add(name); } }); }); return trackNames; } 21
  • 22. Refactoring Legacy Code (step 2) • Example 3-21. Refactor step 2: 内側の forEachブロックを分解 – ネストを解消したい • album → track の stream に変換 → (step3) flatMap 使用 public Set<String> findLongTracks(List<Album> albums) { Set<String> trackNames = new HashSet<>(); albums.stream() .forEach(album -> { album.getTracks() .filter( track -> track.getLength() > 60 ) .map( track -> track.getName() ) .forEach( name -> trackNames.add(name) ); }); return trackNames; } 22
  • 23. Refactoring Legacy Code (step 3) • Example 3-22. Refactor step 3: ネストを解消 – 自分でSetをnewして、各elementをaddしている • → (step 4) collect(toSet()) を使用 – 変数 trackNames を消すことができる public Set<String> findLongTracks(List<Album> albums) { Set<String> trackNames = new HashSet<>(); albums.stream() .flatMap( album -> album.getTracks() ) .filter( track -> track.getLength() > 60 ) .map( track -> track.getName() ) .forEach( name -> trackNames.add(name) ); return trackNames; } 23
  • 24. Refactoring Legacy Code (step 4) • Example 3-23. Refactor step 4: 結果保持用の変数を削除 • まとめ – 段階的にリファクタリング – 各step毎にユニットテストし続けて確認 public Set<String> findLongTracks(List<Album> albums) { return albums.stream() .flatMap( album -> album.getTracks() ) .filter( track -> track.getLength() > 60 ) .map( track -> track.getName() ) .collect( Collectors.toSet() ); } 24
  • 25. Multiple Stream Calls • Example 3-24. Stream の誤用 • Example 3-25. Idiomatically chained stream calls • Example 3-24がExample 3-25より悪い理由 – ビジネスロジックに対して boilerplate code の割合が悪くて読みづらい – 各中間処理でcollectionオブジェクトを作成しているので非効率 – 各中間処理でしか必要としないゴミ変数がメソッドを汚くする – 自動並列化が難しくなる List<Artist> musicians = album.getMusicians() .collect( Collectors.toList() ); List<Artist> bands = musicians.stream() .filter( artist -> artist.getName().startsWith("The") ) .collect( Collectors.toList()); Set<String> origins = bands.stream() .map( artist -> artist.getNationality() ) .collect( Collectors.toSet() ); Set<String> origins = album.getMusicians() .filter( artist -> artist.getName().startsWith("The") ) .map( artist -> artist.getNationality() ) .collect( Collectors.toSet() ); 25
  • 26. Higher-Order Functions • 高階関数 – 他の function を引数や戻り値に出来る関数 – シグニチャを見た時に、functional interface が引数や戻り値に使われていたら、そのメ ソッドは高階関数。 – 例えば mapメソッドは 引数に function を取るので高階関数。 – Streamのほとんど全ての関数は高階関数 – 前に出てきたソートの例では、comparing 関数は、他の関数を引数に取るだけでなく、 戻り値としてComparatorをnewして返していた – Comparator は object と思うかもしれないが、ただ1つの抽象関数を持っている。 • → functional interface 26
  • 27. Good Use of Lambda Expressions (1) • この章で紹介したコンセプト – よりシンプルなコードを書く – operation をデータに記述している • what (何を変換) > how (どのように変換) – バグが発生する可能性を低くするコード – プログラマの意図が分かるコード – 副作用の無い関数 » 何の値 (what values) を返しているのか見るだけで、 » その関数が何をして いるのか (what the functions are doing) » が理解できる 27
  • 28. Good Use of Lambda Expressions (2) – 副作用の無い関数 • 外側から状態を変化させない • 本書の最初のラムダの例では副作用のある例も載っている – コンソールに print out » 観察できる副作用 • では次の例では? – 変数への代入、という副作用を作り出している – プログラムの出力からは見えないかもしれないが、 » プログラムの状態を変更している private ActionEvent lastEvent; private void registerHandler() { button.addActionListener( (ActionEvent event) -> { this.lastEvent = event; } ); } 28
  • 29. Good Use of Lambda Expressions (3) – この例ではローカル変数に代入しようとしている • → 実際はコンパイルできない • capture values > capturing variables – ラムダ式で使用する場合、ローカル変数(上記例ではlocalEvent)に final キーワードが必要ない • けれども、実質的には final (第2章) • ラムダ式を Stream の高階関数に渡す時はいつも、副作用が無いことを目指すべき – 唯一の例外: forEach メソッド • Internal iteration は collection 側に iterate する処理を委譲して、collection を iterate する手段 • Stream は Internal iteration • collection の多くの共通 operation は、ラムダ式を使って、Stream のメソッドを組み合わせる事に よって実行される ActionEvent localEvent = null; button.addActionListener(event -> { localEvent = event; }); Key Points 29