Top Banner
Номын сан Монголын анхны цахим БЗД-ийн 84-р сургууль 2011.04.29
1

ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Jul 05, 2015

Download

Documents

Masatoshi Tada
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

1

ステップ・バイ・ステップで学ぶラムダ式・Stream API入門

多田真敏 @suke_masa

#ccc_h2

JJUG CCC 2014 Fall

Page 2: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

自己紹介

多田真敏(ただまさとし)

某中堅SIerの人材子会社で研修講師

Java(SE 8・EE 7含む)、.NET、ネットワークなど

JJUG CCC登壇は2回連続2回目

前回はJPAネタ→「JPA ロック」で検索

2

Page 3: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Java SE 8 リリースから8か月。

皆さん、お使いですか?

ご自身の学習はいかがですか?

部下・後輩への指導はいかがですか?

このセッションでは、ラムダ式・Stream APIの理解のポイントを解説します

3

Page 4: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

本セッションの対象者

ラムダ式・Stream APIをはじめて学習される方

学習したことはあるものの、今ひとつ腑に落ちないという方

ご自身は理解しているものの、同僚・部下・後輩にどうやって教えたらよいか悩んでいる方

4

Page 5: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Java SE 8の超重要情報

Java SE 8 ローンチイベントの動画徹底解説!Project Lambdaのすべて リターンズ(@bitter_foxさん)

https://www.youtube.com/watch?v=gAMYhTl7t70

from old Java to modern Java - reloaded(@cero_tさん)

https://www.youtube.com/watch?v=aLRonTjIeFI

Web記事詳解 Java SE 8(@skrbさん)

http://itpro.nikkeibp.co.jp/article/COLUMN/20140212/536246/

書籍Java SE 8実践プログラミング(以下「実践本」)Javaによる関数型プログラミング(以下「関数本」)Javaエンジニア養成読本(以下「養成本」)

5

Page 6: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

こんなコードが出てくる

6

public static Map<Character, Integer> countByInitial() {return EMP_LIST.stream()

.collect(Collector.of(() -> IntStream.rangeClosed('A', 'Z')

.mapToObj(i -> (char) i)

.collect(Collectors.toMap(c -> c, c -> 0)),(map, emp) -> map.compute(emp.getName().charAt(0),

(initial, count) -> ++count),(map1, map2) -> {

map1.forEach((initial, count) -> map1.put(initial, count + map2.get(initial));

return map1;},map -> Collections.unmodifiableMap(map)));

}

Page 7: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

階段が高すぎては登れない

7

超えられない壁

Page 8: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

1段ずつ登りましょう!

8

Page 9: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

理解するためのステップ

①ラムダ式を読み書きできる

② Streamの生成を理解する

③中間操作でデータ操作ができる

④終端操作で好きな値に変換できる

⑤何でもかんでもラムダ式・Stream APIで書ける

9

Page 10: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

10

ステップ1:ラムダ式を読み書きしよう!

Page 11: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Java SE 8は「関数型言語」?

ラムダ式・Stream APIの登場

関数型言語Scalaと比較された記事あり

Java SE 8の書籍には「遅延実行」「高階関数」など、関数型言語の用語がズラリ・・・

11

Page 12: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Javaは「オブジェクト指向言語」

ラムダ式は、オブジェクト指向言語の範囲を超えないように設計されている

12

今までのJavaを理解していれば、

必ず理解できる!

Page 13: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

無名クラス

13

interface Calculator {int calc(int a, int b);

}

Calculator c = new Calculator() {public int calc(int a, int b) {

return a + b;}

};

int result = c.calc(1, 2);

無名クラス

Page 14: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ポイント① コンパイラの推論強化

コンパイラが、いろんな部分を推論してくれる

「Enhancements in Java SE 8」では、ラムダ式に続いて2番目に挙げられている

https://docs.oracle.com/javase/8/docs/technotes/guides/language/enhancements.html#javase8

今まで書いていた冗長な部分を省略可能→ラムダ式!

14

Page 15: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

推論による省略

15

interface Calculator {int calc(int a, int b);

}

Calculator c = new Calculator() {public int calc(int a, int b) {

return a + b;}

};

int result = c.calc(1, 2);

左辺から推論可能

抽象メソッドが1つのみ(=関数型インターフェイス)

ならば推論可能

Page 16: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ラムダ式による記述

16

interface Calculator {int calc(int a, int b);

}

Calculator c = (a, b) -> { return a + b; };

int result = c.calc(1, 2);

アロー記号でつなぐ

Page 17: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

更なる省略

17

interface Calculator {int calc(int a, int b);

}

Calculator c = (a, b) -> a + b;

int result = c.calc(1, 2);

メソッド本文が1文のみならば、return、{}、; が省略可能戻り値の有無によらない

引数が1つの場合、() が省略可能引数無しの場合は省略不可

Page 18: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

メソッドの引数にラムダ式を指定

18

interface Calculator {int calc(int a, int b);

}

void print(int n1, int n2, Calculator c) {System.out.println(“result = ” + c.calc(n1, n2))

}

print(1, 2, (a, b) -> a + b);

ラムダ式をメソッドの引数に渡すことも可能

引数の型から推論する

Page 19: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ジェネリクスに関する型推論強化

19

List<String> list = new ArrayList<>();list.add("hoge");list.addAll(Arrays.asList());

Arrays.asList(T… a)の戻り値はList<T>

Tは通常、asList()の引数の型から推論する

addAll()の引数はCollection<? extends String>

コンパイラが「T=String」と推論してくれる

Java SE 7だとコンパイルエラー(Tに関する情報が無いので、T=Objectと推論される)

Page 20: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ジェネリクスに関する型推論強化

20

interface Function<T, R> {R apply(T t);

}

void print(A a, Function<A, B> func) {B b = func.apply(a);System.out.println(b)

}

print("hoge", str -> str.length()); // 「4」と表示

ラムダ式の戻り値から推論

B = IntegerA = String

Page 21: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ポイント② ラムダはインスタンス

処理を書いているように見えるが、実はそのようなメソッドを持つ無名クラスのインスタンスを生成しているだけ

21

※さらに詳しく知りたい方は@bitter_foxさんの動画をご覧ください(匿名クラスとの違い 13:30~、ターゲット型 19:40~)

Page 22: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

なぜラムダ式は「難しい」のか?

ラムダ式の引数の型を省略した場合、「この変数ってどこで宣言されているの!?」となってしまう

22

doSomething(a -> a + 10);

aはどこで宣言されてるんだ!?→宣言を探しても見つからない

→「ラムダ式難しい」

// 実はコレの省略形doSomething((int a) -> {return a + 10;});

Page 23: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Javaプログラマの頭の中(Java 7以前)

23

クラス メソッド変数の宣言

変数の利用

名前の頭文字が大文字

後ろに()がついている

先頭にデータ型が書いてある

名前を見つける

Page 24: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Javaプログラマの頭の中(Java 8以降)

24

クラス メソッド変数の宣言

変数の利用

名前の頭文字が大文字

後ろに()がついている

先頭にデータ型が書いてある

後ろに ->がついている

ラムダの仮引数

名前を見つける

頭の中のフローチャートを書き換える必要がある!

Page 25: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ポイント③ 脳内フローチャート変更

データ型が付いていない変数は、ラムダの引数である可能性がある

アロー記号の有無を必ずチェック

脳内でデータ型を追加しよう

25

コード a -> a + 10;↓脳内 (int a) -> { return a + 10; };

Page 26: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

なぜラムダ式は「難しい」のか?

ラムダ式が値を返しているのかどうかが読み取れない

26

StringBuilder builder = new StringBuilder();doSomething(builder, sb -> sb.append("hoge"));

append()してるだけ?それとも戻り値を返しているの?

→分からない→「ラムダ式難しい」

Page 27: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ポイント④ ラムダ省略禁止

慣れていないうちは、仮引数の型、()、{}、returnは省略せずに書く

「ラムダ禁止」ではありません

慣れてきたら、徐々に省略すればよい

27

× a -> a + 10;

○ (int a) -> { return a + 10; };

Page 28: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

なぜラムダ式は「難しい」のか?

自分の書いたラムダが、どんな目的で、どんなタイミングで実行されるかが分からない

ラムダの引数に、具体的にどんな値が入ってくるのかが分からない

28

Map<String, Integer> map = new HashMap<>();map.put(“apple”, 20);map.put(“orange”, 30);int value = map.compute(“apple”, (k, v) -> v + 10);

k、vには何が入るの!? このラムダは何に使われるの!?

Page 29: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

なぜラムダ式は「難しい」のか?

汎用関数型インターフェイスがよく分からない

ジェネリクスが多すぎてよく分からない

29

BiFunctionって何!? ジェネリクス大杉!!→分からない→「ラムダ式難しい」

※super、extendsの意味について知りたい方は、「実践本」のP.74~P.76をご覧ください

Page 30: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ポイント⑤ 汎用関数型インターフェイス

BiFunction<T, U, R>は、TとUを引数とし、Rを戻り値とするメソッドを持つ関数型インターフェイス

30

public interface BiFunction<T, U, R> {R apply(T t, U u);

}

※本当はもう1つデフォルトメソッドがありますが、省略しています

Page 31: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ポイント⑤ 汎用関数型インターフェイス

ジェネリクスについても、コンパイラの推論が強化されている

31

Map<String, Integer> map = new HashMap<>();・・・String s = map.compute(“apple”, (k, v) -> v + 10);

K V

String Integer Integer

第1引数 第2引数 戻り値

Page 32: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

汎用関数型インターフェイス一覧

32

関数型インターフェイス メソッド

Runnable void run()

Supplier<T> T get()

Consumer<T> void accept(T t)

BiConsumer<T, U> void accept(T t, U u)

Function<T, R> R apply(T t)

BiFunction<T, U, R> R apply(T t, U u)

UnaryOperator<T> T apply(T t)

BinaryOperator<T> T apply(T t1, T t2)

Predicate<T> boolean test(T t)

BiPredicate<T, U> boolean test(T t, U u)

※Runnable以外は、すべてjava.util.functionパッケージ

Page 33: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

基本データ型用の関数型インターフェイス

33

関数型インターフェイス メソッド

BooleanSupplier boolean getAsBoolean()

P Supplier p getAsP ()

P Consumer void accept(p p)

ObjP Consumer<T> void accept(T t, p p)

P Function<T> T apply(p p)

P ToQ Function q applyAsQ (p p)

ToP Function<T> p applyAsP (T t)

ToP BiFunction<T, U> p applyAsP (T t, U u)

P UnaryOperator p applyAsP (p p)

P BinaryOperator p applyAsP (p p1, p p2)

P Predicate boolean test(p p)

※「実践本」より抜粋※p とq は、int、long、double。P とQ は、Int、Long、Double

Page 34: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ポイント⑥ ソースコードを読む

ラムダ式を引数に取るメソッドのソースコードを読んで、どのように実行されているかを見る

34

default V compute(K key, Function<K, V> remappingFunction) {V oldValue = get(key);V newValue = remappingFunction.apply(key, oldValue);put(key, newValue);return newValue;

}

Map#compute()のソース(単純化しています)

Map<String, Integer> map = new HashMap<>();・・・int value = map.compute(“apple”, (k, v) -> v + 10);

Page 35: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ポイント⑦ デバッグプリント

ラムダ式内にデバッグプリントを入れて、引数に何が入っているかを確認する

35

String s = map.compute(“apple”, (String k, Integer v) -> {System.out.println(“k = ” + k + “, v = ” + v);return v + 10;

});

Page 36: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

36

ステップ2:Streamを生成しよう!

Page 37: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Stream APIとは?

コレクションを操作するためのAPI

データの抽出・加工・集計などを行う

Stream内には基本的にデータは持たない

元のコレクションは変えない

37

Stream<T>

元のコレクション コレクション

合計・平均

Collection<T>、T[]

Page 38: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

生成・中間操作・終端操作

38

public static List<Emp> sortBySalaryAsc() {// Streamの生成return EMP_LIST.stream()

// 中間操作.sorted((Emp e1, Emp e2)

-> { return e1.getSalary() - e2.getSalary(); })// 終端操作.collect(Collectors.toList());

}

Streamの生成コレクションなどのソースからStreamを生成する

中間操作ソート・フィルタリング・変換などを行い、Streamを返す。複数回実行可能

終端操作結果をコレクションなどに集約する。1回のみ実行可能

Page 39: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Streamの生成

コレクションから生成

39

List<String> list = Arrays.asList("hoge", "fuga", "foo");Stream<String> stream = list.stream();

可変長引数で生成Stream<String> stream = Stream.of("hoge", "fuga", "foo");

配列から生成String[] strings = {"hoge", "fuga", "foo"};Stream<String> stream = Arrays.stream(strings);

その他Stream.generate()、Stream.iterate()、Files.lines()、・・・

Page 40: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

パラレルStreamの生成

parallelStream()メソッドを利用

40

List<String> list = Arrays.asList("hoge", "fuga", "foo");Stream<String> stream = list.paralellStream();

parallel()メソッドを利用Stream<String> stream = Stream.of("hoge", "fuga", "foo");Stream<String> paraStream = stream.parallel();

Page 41: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

基本データ型用のStream

IntStream

LongStream

DoubleStream

中間操作でStream<T>などと相互変換可能(後述)

41

Page 42: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

IntStreamの生成

1~9まで

42

IntStream intStream = IntStream.range(1, 10);

配列から生成int[] ints = {1, 2, 3, 4, 5};IntStream intStream = Arrays.stream(ints);

その他IntStream.generate()、IntStream.iterate()、・・・

1~10までIntStream intStream = IntStream.rangeClosed(1, 10);

Page 43: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

charのStream

用意されていないので、Stream<Character>を利用する

43

// A~ZまでのIntStreamを生成IntStream intStream = IntStream.rangeClosed('A', 'Z');

// 中間操作でStreamに変換Stream<Character> charStream

= intStream.mapToObj((int i) -> { return (char) i; });

Page 44: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

44

ステップ3:中間操作でデータを操作しよう!

Page 45: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

サンプルプログラム

社員クラス

45

public class Emp {

private int id; // 社員番号private String name; // 社員名private int salary; // 給与private Dept dept; // 部署を表すEnum

// setter/getter、コンストラクタ、toString()は省略}

public enum Dept {ADMIN, PLANNING, SALES, OPERATIONS, NONE;

}

Page 46: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

サンプルプログラム

46

public class EmpDB {private static final List<Emp> EMP_LIST = Arrays.asList(

new Emp(101, "Nishida", 500000, Dept.ADMIN),new Emp(102, “Nohira”, 285000, Dept.SALES),new Emp(103, "Kiyama", 245000, Dept.ADMIN),new Emp(104, "Ohkawa", 297500, Dept.PLANNING),new Emp(105, "Kajiyama", 125000, Dept.SALES),new Emp(106, "Kohsaka", 160000, Dept.SALES),new Emp(107, "Nishikawa", 150000, Dept.SALES),new Emp(108, "Sasaki", 95000, Dept.SALES),new Emp(109, "Ichikawa", 125000, Dept.SALES),new Emp(110, "Yamamoto", 300000, Dept.PLANNING),new Emp(111, "Komatsu", 80000, Dept.PLANNING),new Emp(112, "Aizawa", 300000, Dept.PLANNING),new Emp(113, "Saitoh", 110000, Dept.NONE),new Emp(114, "Inoue", 130000, Dept.ADMIN));

// ここにメソッドを書く}

Page 47: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Stream#filter()メソッド

Stream<T> filter(Predicate<T> predicate)

Stream内の全要素(T)に条件predicateを指定し、それがtrueとなる要素のみ抽出する

47

public interface Predicate<T> {boolean test(T t);

}

※本当の引数は「Predicate<? super T>」ですが、読みづらいため省略しています。<? super T>の意味が知りたい方は実践本P.74~P.76をご覧ください

Page 48: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

特定の部署で抽出する

48

public static List<Emp> filterByDept() {// Streamを生成Stream<Emp> stream = EMP_LIST.stream();// 部署がSALESの社員のみ抽出(中間操作)Stream<Emp> filteredStream

= stream.filter((Emp emp) -> { return emp.getDept() == Dept.SALES; });

// 結果をリストに集約(終端操作)List<Emp> result = filteredStream.collect(Collectors.toList());

}

Emp{id=102, name=Nohira, salary=285000, dept=SALES}Emp{id=105, name=Kajiyama, salary=125000, dept=SALES}Emp{id=106, name=Kohsaka, salary=160000, dept=SALES}Emp{id=107, name=Nishikawa, salary=150000, dept=SALES}Emp{id=108, name=Sasaki, salary=95000, dept=SALES}Emp{id=109, name=Ichikawa, salary=125000, dept=SALES}

Page 49: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

メソッドチェーン&ラムダ省略で書く

49

public static List<Emp> filterByDept() {return EMP_LIST.stream()

.filter(emp -> emp.getDept() == Dept.SALES)

.collect(Collectors.toList());}

下記のように記述することが一般的

型の変換が見えにくいため、慣れないうちはあまりおススメできない

Page 50: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ポイント⑧ メソッドチェーン禁止

50

1行1行ていねいに書く!

慣れてきたらメソッドチェーンで書けばよい

Page 51: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Stream#map()メソッド

Stream<R> map(Function<T, R> mapper)

Stream内の全要素をapply()メソッドの引数に渡し、その戻り値から成るStreamを返す

apply()でreturnした値の型がRとなる

51

public interface Function<T, R> {R apply(T t);

}

Page 52: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

名前だけのリストに変換する

52

public static List<String> getNameList() {Stream<Emp> stream = EMP_LIST.stream();Stream<String> nameStream

= stream.map((Emp emp) -> { return emp.getName();});List<String> result = nameStream.collect(Collectors.toList());return result;

}

NishidaNohiraKiyamaOhkawaKajiyamaKohsakaNishikawa・・・

Page 53: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

map()による型変換

53

Stream<Emp> stream = EMP_LIST.stream();Stream<String> nameStream

= stream.map((Emp emp) -> { return emp.getName();});

戻り値の型がString

型引数(R)がStringに決まる

public class EmpNameMapper implements Function<Emp, String> {public String apply(Emp emp) {

return emp.getName();}

}

こんなイメージ

Page 54: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ポイント⑨ 「R」は自分で決められる型

54

「T」のような自動的に決まる型と、「R」のような自分で決められる型がある!

Page 55: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

IntStreamとの相互変換

55

Stream<T>

IntStream

mapToInt() mapToObj()

Page 56: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

IntStreamとの相互変換

56

Stream<Emp> stream = EMP_LIST.stream();IntStream salaryStream

= stream.mapToInt((Emp emp) -> { return emp.getSalary();})

IntStream idStream = IntStream.rangeClosed(101, 110);Stream<Emp> empStream

= idStream.mapToObj((int id) -> { return new Emp(id);})

IntStream mapToInt(ToIntFunction<T> mapper)Streamの要素(T)が引数、戻り値がintであるラムダを指定

Stream<T> mapToObj(IntFunction<T> mapper)IntStreamの要素(int)が引数、戻り値がTであるラムダを指定

intを返すように指定

オブジェクトを返すように指定

Page 57: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

中間操作と終端操作が分かれている理由

繰り返しの回数を減らすためfilter()する度に繰り返しを行っていたら効率が悪い(下左のようなコードになる)

終端操作の際に、全ての中間操作をまとめて実行

57

for (Emp emp : list) {if (emp.getDept() == Dept.SALES) {

// 処理}

}for (Emp emp : list) {

if (emp.getSalary() >= 150000) {// 処理

}}

for (Emp emp : list) {if (emp.getDept() == Dept.SALES) {

// 処理}if (emp.getSalary() >= 150000) {

// 処理}

}

繰り返し2回→効率× 繰り返し1回→効率○

※このコードはイメージです

Page 58: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ポイント⑩ 中間操作はsetterと考える

58

public class Stream<T> {private Predicate<T> pred;public Stream<T> filter(Predicate<T> pred) {

this.pred = pred;}public R collect(・・・) {

for (T t : elements) {if (pred.test(t)) {

// tを結果に追加}

}}

}

※このコードはイメージです

中間操作の時点では、条件は中に保持するだけ

終端操作の時点で、条件を実行

これを「遅延実行」と呼んでいるだけ

Page 59: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

その他の中間操作

Stream<T> sorted(Comparator<T> comparator)

要素を並び替える

Stream<R> flatMap(Function<T, Stream<R>> mapper)

各要素からStreamを生成し、それらを一本化する

59

Page 60: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

その他の中間操作

Stream<T> distinct()重複を削除する(Object#equals()がtrueとなるものは重複と見なされる)

Stream<T> limit(long maxSize)最初の要素から、最大maxSize個までの要素のみを含んだStreamを返す

Stream<T> skip(long n)最初の要素から数えてn個の要素を破棄し、残りの要素のみを含んだStreamを返す

Stream<T> peek(Consumer<T> action)forEach()の中間操作バージョン(デバッグが主目的)

60

Page 61: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

中間操作が分かると・・・

読める行数が飛躍的に増加

61

public static List<String> filterByDeptAndSalarySortById() {return EMP_LIST.stream()

// DeptがSALESの人のみ抽出.filter(emp -> emp.getDept() == Dept.SALES)// 給与が150000以上の人のみ抽出.filter(emp -> emp.getSalary() >= 150000)// IDの昇順で並び替え.sorted((e1, e2) -> e1.getId() - e2.getId())// 「ID : 給与」という文字列に変換.map(emp -> emp.getId() + " : " + emp.getSalary())// 文字列のリストに集約.collect(Collectors.toList());

}

Page 62: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

62

ステップ4:終端操作で好きな値に変換しよう!

Page 63: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Stream#collect()メソッド

Streamを任意のコレクションなどに集約する

引数はCollectorインターフェイス(後述)

63

Page 64: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Collectorsクラス

汎用のCollectorを返すファクトリークラス

主なメソッドtoList()リストに変換するCollectorを返す

toMap()マップに変換するCollectorを返す

groupingBy()マップにグループ化するCollectorを返す

counting()要素の個数を数えるCollectorを返す

64

Page 65: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Collectors.toList()メソッド

65

public static List<Emp> filterByDept() {Stream<Emp> stream = EMP_LIST.stream();Stream<Emp> filteredStream

= stream.filter((Emp emp) -> { return emp.getDept() == Dept.SALES; });

List<Emp> result = filteredStream.collect(Collectors.toList());}

Page 66: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Collectors.toMap()メソッド

toMap(Function<T, K> keyMapper,Function<T, U> valueMapper)

T:Streamの要素の型K:マップのキーとなる型(自分で決められる)U:マップの値となる型(自分で決められる)

例:社員IDがキー、社員名が値のマップに変換

66

public static Map<Integer, String> getIdNameMap() {Stream<Emp> stream = EMP_LIST.stream();Map<Integer, String> map = stream.collect(Collectors.toMap(

(Emp emp) -> { return emp.getId();}, // マップのキー(Emp emp) -> { return emp.getName();})); // マップの値

return map;}

Page 67: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Collectors.groupingBy()メソッド

groupingBy(Function<T, K> classifier)T:Streamの要素の型K:マップのキーとなる型(自分で決められる)

例:部署がキー、その部署に所属する社員のリストが値のマップに変換

67

public static Map<Dept, List<Emp>> groupByDept() {Stream<Emp> stream = EMP_LIST.stream();Map<Dept, List<Emp>> map

= stream.collect(Collectors.groupingBy((Emp emp) -> { return emp.getDept();})); // マップのキー

return map;}

Page 68: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Collectors.groupingBy()メソッド

groupingBy(Function<T, K> classifier, Collector<T, A, D> downstream)

T:Streamの要素の型K:マップのキーとなる型(自分で決められる)A、Dは後述

例:部署がキー、その部署に所属する社員の人数が値のマップに変換

68

public static Map<Dept, Long> groupByDeptAndCounting() {Stream<Emp> stream = EMP_LIST.stream();Map<Dept, Long> map = stream.collect(Collectors.groupingBy(

(Emp emp) -> { return emp.getDept();}, // マップのキーCollectors.counting())); // マップの値

return map;}

Page 69: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Collectors.groupingBy()メソッド

例:部署がキー、その部署に所属する社員の給与の合計が値のマップに変換

69

public static Map<Dept, Long> groupByDeptAndSummingSal() {Stream<Emp> stream = EMP_LIST.stream();Map<Dept, Long> map = stream.collect(Collectors.groupingBy(

(Emp emp) -> { return emp.getDept();}, // マップのキーCollectors.summingLong(

(Emp emp) -> { return emp.getSalary(); } ))); // マップの値return map;

}

Page 70: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ポイント⑪ 好きな型に集約する手順

① 要素(T)を蓄積するコンテナ(A)を生成する→サプライヤで生成

② 要素(T)をコンテナ(A)に蓄積する→アキュムレータで蓄積

③ コンテナ(A)をマージする(パラレル実行の場合)→コンバイナでマージ

④ コンテナ(A)を最終的に返す型(R)に変換する→フィニッシャで変換(コンテナをそのまま返す場合は不要)

70

Page 71: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Collector<T, A, R>インターフェイス

T:Streamの要素、A:コンテナ、R:最終的に返す型

4つのメソッドを持つSupplier<A> supplier()BiConsumer<A, T> accumulator()BinaryOperator<A> combiner()Function<A, R> finisher()

Collector.of()メソッドで、実装クラス(Collectors.CollectorImplクラス)のインスタンスを生成して返す

71

Page 72: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Collectors.toList()のソースを読む

72

public static <T> Collector<T, ArrayList<T>, List<T>> toList() {Supplier<ArrayList<T>> supplier = () -> {

return new ArrayList<>(); // コンテナを生成};BiConsumer<ArrayList<T>, T> accumulator = (ArrayList<T> list, T t) -> {

list.add(t); // 要素をコンテナに蓄積};BinaryOperator<ArrayList<T>> combiner

= (ArrayList<T> list1, ArrayList<T> list2) -> {list1.addAll(list2); // コンテナをマージreturn list1;

};Function<ArrayList<T>, List<T>> finisher = (ArrayList<T> list) -> {

return (List<T>) list; // コンテナを最終的に返す型に変換};return Collector.of(supplier, accumulator, combiner, finisher);

}

※このコードはイメージです

Page 73: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Collectors.toMap()のソースを読む

73

public static <T, K, U> Collector<T, HashMap<K, U>, Map<K,U>> toMap(Function<T, K> keyMapper, Function<T, U> valueMapper) {

Supplier<HashMap<K, U>> supplier = () -> { return new HashMap<>(); // コンテナを生成

};BiConsumer<HashMap<K, U>, T> accumulator

= (HashMap<K, U> map, T t) -> {map.put(keyMapper.apply(t), valueMapper.apply(t)); // 要素をコンテナに蓄積

};BinaryOperator<HashMap<K, U>> combiner

= (HashMap<K, U> map1, HashMap<K, U> map2) -> {/* 長いので省略 */ // コンテナをマージ

};Function<HashMap<K, U>, Map<K, U>> finisher = (HashMap<K, U> map) -> {

return (Map<K, U>) map; // コンテナを最終的に返す型に変換};return Collector.of(supplier, accumulator, combiner, finisher);

}

※このコードはイメージです

Page 74: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Stream#collect()のソースを読む

74

public <T, A, R> R collect(Collector<T, A, R> collector) {Supplier<A> supplier = collector.supplier();A container = supplier.get(); // コンテナを生成BiConsumer<A, T> accumulator = collector.accumulator();forEach((T t) -> {

accumulator.accept(container, t); // 要素をコンテナに蓄積});BinaryOperator<A> combiner = collector.combiner();container = combiner.apply(container1, container2); // コンテナをマージFunction<A, R> finisher = collector.finisher();R result = finisher.apply(container); // コンテナを最終的に返す型に変換return result;

}

※このコードはイメージです

Page 75: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

最初のコード(再掲)

75

public static Map<Character, Integer> countByInitial() {return EMP_LIST.stream()

.collect(Collector.of(() -> IntStream.rangeClosed('A', 'Z')

.mapToObj(i -> (char) i)

.collect(Collectors.toMap(c -> c, c -> 0)),(map, emp) -> map.compute(emp.getName().charAt(0),

(initial, count) -> ++count),(map1, map2) -> {

map1.forEach((initial, count) -> map1.put(initial, count + map2.get(initial));

return map1;},map -> Collections.unmodifiableMap(map)));

}

Page 76: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

やりたいこと(仕様)

List<Emp>をMap<Character, Integer>に変換する

Mapのキーは、A~Zまでのイニシャル

Mapの値は、そのイニシャルの人数

そのイニシャルがいない場合、値は0

結果のMapはイミュータブル(変更不可能)

76

Page 77: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

①サプライヤ

Map<Character, Integer>を生成→A~Zまでの文字をキーとし、各値に0をput

77

Supplier<Map<Character, Integer>> supplier = () -> {IntStream stream = IntStream.rangeClosed('A', 'Z');Stream<Character> charStream

= stream.mapToObj((int i) -> { return (char) i; });Map<Character, Integer> map

= charStream.collect(Collectors.toMap((Character c) -> { return c; }, (Character c) -> { return 0; }));

return map;};

Page 78: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

②アキュムレータ

①のMapとEmpが引数。Empのnameからイニシャルを取得し、それに対応するMapの値をインクリメント

78

BiConsumer<Map<Character, Integer>, Emp> accumulator = (Map<Character, Integer> map, Emp emp) -> {

char initial = emp.getName().charAt(0);map.compute(initial, (Character k, Integer v) -> ++v);

};

Page 79: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

③コンバイナ

2つのMapをマージする。一方のMapの全キーに対して、2つのMapの合計値をput

79

BinaryOperator<Map<Character, Integer>> combiner = (Map<Character, Integer> map1,

Map<Character, Integer> map2) -> {map1.forEach((Character initial, Integer count1) -> {

int count2 = map2.get(initial);map1.put(initial, count1 + count2);

});return map1;

};

Page 80: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

④フィニッシャ

Mapをイミュータブルに変換する

80

Function<Map<Character, Integer>, Map<Character, Integer>> finisher =

(Map<Character, Integer> map) -> { return Collections.unmodifiableMap(map); };

Page 81: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

まとめると、こうなる

81

public static Map<Character, Integer> countByInitial() {Supplier<Map<Character, Integer>> supplier = ・・・;BiConsumer<Map<Character, Integer>, Emp> accumulator = ・・・;BinaryOperator<Map<Character, Integer>> combiner = ・・・;Function<Map<Character, Integer>, Map<Character, Integer>>

finisher = ・・・;

Collector<Emp, Map<Character, Integer>, Map<Character, Integer>> collector = Collector.of(supplier, accumulator, combiner, finisher);

return EMP_LIST.stream().collect(collector);}

Page 82: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

無理やり1文にすると、こうなる

82

public static Map<Character, Integer> countByInitial() {return EMP_LIST.stream()

.collect(Collector.of(() -> IntStream.rangeClosed('A', 'Z')

.mapToObj(i -> (char) i)

.collect(Collectors.toMap(c -> c, c -> 0)),(map, emp) -> map.compute(emp.getName().charAt(0),

(initial, count) -> ++count),(map1, map2) -> {

map1.forEach((initial, count) -> map1.put(initial, count + map2.get(initial));

return map1;},map -> Collections.unmodifiableMap(map)));

}

Page 83: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

ポイント⑫ Collectorを自作する場合

サプライヤなどは、一旦変数に入れた方が可読性が上がる

無理に1文で書くと読みづらい

閉じカッコの数が合わなかったりして地味に辛い

IDEが真っ赤っかになってコンパイルエラーの箇所が特定しづらい

83

Page 84: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

その他の終端操作

Optional<T> reduce(BinaryOperator<T> accumulator)全要素に対して順に演算を行う

long count()Streamに含まれる要素数を返す

Optional<T> max(Comparator<T> comparator)指定したComparatorの順で最大(順番が最後)の値を返す(Streamが空の場合は、空のOptional)

Optional<T> min(Comparator<T> comparator)指定したComparatorの順で最小(順番が最初)の値を返す(Streamが空の場合は、空のOptional)

84

Page 85: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

その他の終端操作

boolean allMatch(Predicate<T> predicate)

Streamに含まれる全要素が引数の条件に合致する場合、trueを返す

boolean anyMatch(Predicate<T> predicate)

Streamに含まれるいずれかの要素が引数の条件に合致する場合、trueを返す

boolean noneMatch(Predicate<T> predicate)

Streamに含まれる全要素が引数の条件に合致しない場合、trueを返す

85

Page 86: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

その他の終端操作

Optional<T> findFirst()

Stream内の各要素を順番に検索し、条件(filter()などで指定)に合致した最初の要素を返す

Optional<T> findAny()

Stream内の各要素を並列で検索し、条件(filter()などで指定)に合致した、最初に見つかった要素を返す

実行の度に戻り値が異なる場合があるので注意

86

Page 87: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

87

ステップ5:何でもかんでもラムダ式・Stream APIで書こう!

Page 88: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

汎用関数型インターフェイスとラムダ式の練習

88

わざとFunctionやPredicateを使って書いてみる

ラムダ式と汎用関数型インターフェイスを理解する練習になる

String name = emp.getName();↓Function<Emp, String> func = e -> e.getName();String name = func.apply(emp);

if (emp.getSalary() >= 150000) { ・・・ }↓Predicate<Emp> pred = e -> e.getSalary() >= 150000if (pred.test(emp)) { ・・・ }

Page 89: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

汎用関数型インターフェイスとラムダ式の練習

89

Functionなどを引数とするメソッドを自作する

Page 90: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Java SE 8での定石

forやwhileを見たらStream APIへの置き換えを考える!(@cero_tさん)

forEach()使ったら負け! (@skrbさん)

null書いたら負け! (@kisさん)

90

Page 91: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

繰り返しをStream APIで書き換える練習

1~20までの偶数のみの和を求める

91

int sum = IntStream.rangeClosed(1, 20).filter(i -> i % 2 == 0) .sum(); int sum = IntStream.rangeClosed(1, 10).map(i -> i * 2) .sum(); int sum = IntStream.rangeClosed(1, 10).map(i -> i * 2)

.reduce(0, (i1, i2) -> i1 + i2);

FizzBuzz

List<String> list = IntStream.rangeClosed(1, 40).mapToObj(i -> i % 15 == 0 ? "FizzBuzz"

: i % 3 == 0 ? "Fizz" : i % 5 == 0 ? "Buzz"

: String.valueOf(i)).collect(Collectors.toList());

Page 92: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

「何でもかんでもラムダ式・Stream APIで書けばいいって訳じゃないよねー」

とは言いますが、でもそれって

「何でもかんでもラムダ式・Stream APIで書ける実力」があってこそではないでしょうか?(自戒も込めて・・・)

まずはチャレンジしてみてください!

92

Page 93: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

あの日を思い出してください

初めてプログラミング言語に触れたあの日・・・

何でもかんでも順次・分岐・繰り返しで書いて、楽しんでいたのではないでしょうか?

その気持ちを思い出して、何でもかんでもラムダ式・Stream APIで書いてみてください

そのうちに自然と身に付きます!

93

Page 94: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

最後のまとめ

慣れるまでは、ラムダ式は引数の型・returnなどを省略せずに書こう!

Stream APIは、無理してメソッドチェーンを使わないでOK。まずは型の変換をしっかり理解しよう!

Collectorインターフェイスを極めよう!

何でもかんでもラムダ式・Stream APIで書いて、どんどん慣れていこう!

94

Page 95: ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

Enjoy Java SE 8 !!

95

ご清聴ありがとうございました!