More Related Content Similar to ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2 Similar to ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2 (20) More from Masatoshi Tada (11) ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h23. Java SE 8 リリースから8か月。
皆さん、お使いですか?
ご自身の学習はいかが
ですか?
部下・後輩への指導は
いかがですか?
このセッションでは、ラム
ダ式・Stream APIの理解
のポイントを解説します
3
5. 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
6. こんなコードが出てくる
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)));
}
13. 無名クラス
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);
無名クラス
15. 推論による省略
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つのみ
(=関数型インターフェイス)
ならば推論可能
17. 更なる省略
17
interface Calculator {
int calc(int a, int b);
}
Calculator c = (a, b) -> a + b;
int result = c.calc(1, 2);
メソッド本文が1文のみならば、return、{}、; が
省略可能
戻り値の有無によらない
引数が1つの場合、() が省略可能
引数無しの場合は省略不可
18. メソッドの引数にラムダ式を指定
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);
ラムダ式をメソッドの引数に渡すことも可能
引数の型から推論する
19. ジェネリクスに関する型推論強化
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と推論される)
20. ジェネリクスに関する型推論強化
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
32. 汎用関数型インターフェイス一覧
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パッケージ
33. 基本データ型用の関数型インターフェイス
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
34. ポイント⑥ ソースコードを読む
ラムダ式を引数に取るメソッドのソースコードを
読んで、どのように実行されているかを見る
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);
38. 生成・中間操作・終端操作
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回のみ実行可能
39. 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()、・・・
42. 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);
45. サンプルプログラム
社員クラス
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;
}
46. サンプルプログラム
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));
// ここにメソッドを書く
}
48. 特定の部署で抽出する
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}
52. 名前だけのリストに変換する
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;
}
Nishida
Nohira
Kiyama
Ohkawa
Kajiyama
Kohsaka
Nishikawa
・・・
53. 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();
}
}
こんなイメージ
56. 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を返すように指定
オブジェクトを返すように指定
58. ポイント⑩ 中間操作は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を結果に追加
}
}
}
}
※このコードはイメージです
中間操作の時点では、
条件は中に保持するだけ
終端操作の時点で、
条件を実行
これを「遅延実行」と呼んでいるだけ
61. 中間操作が分かると・・・
読める行数が飛躍的に増加
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());
}
65. 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());
}
66. 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;
}
68. 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;
}
72. 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);
}
※このコードはイメージです
73. 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);
}
※このコードはイメージです
74. 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;
}
※このコードはイメージです
75. 最初のコード(再掲)
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)));
}
81. まとめると、こうなる
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);
}
82. 無理やり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)));
}
91. 繰り返しを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());