Java SE 8 lambdaで変わる プログラミングスタイル

15,755 views
15,609 views

Published on

福岡JavaOne2013報告会第2弾でのプレゼン
ラムダ構文の文法よりも、その使い方とプログラミングスタイルについてを主にまとめました。

Published in: Technology, News & Politics
0 Comments
28 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
15,755
On SlideShare
0
From Embeds
0
Number of Embeds
10,680
Actions
Shares
0
Downloads
78
Comments
0
Likes
28
Embeds 0
No embeds

No notes for slide

Java SE 8 lambdaで変わる プログラミングスタイル

  1. 1. Java SE 8 lambdaで変わる プログラミングスタイル 2013/11/15 きしだなおき
  2. 2. ラムダがきたよ ● ● ● 匿名関数 関数型スタイルには必須 流行!
  3. 3. JavaOneでのラムダ ● ● ● 関連セッションが大人気!! どのセッションも行列!! 人数オーバーで入れない!!
  4. 4. ラムダの目的 ● ● 建前:並列化 本音:Cool!!
  5. 5. ラムダ構文 ● ● ● ● 関数型インタフェース ラムダ記法 メソッド参照 interfaceのデフォルトメソッド
  6. 6. 関数型インタフェース ● 実装すべきメソッドがひとつだけのインタフェース – Runnable ● – ActionListener ● ● 実装すべきメソッド:run() 実装すべきメソッド:actionPerformed(ActionEvent) @FuncationalInterfaceで検査可能
  7. 7. 用意された関数型インタフェース http://d.hatena.ne.jp/nowokay/20130824#1377300917
  8. 8. ラムダ構文の基礎 ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "n"); } };
  9. 9. ラムダ構文の基礎 ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "n"); } }; インタフェース名やメソッド名は推論される
  10. 10. ラムダ構文の基礎 ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "n"); } }; 引数とメソッド本体の間に「->」が入る ->
  11. 11. ラムダ構文の基礎 ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "n"); } }; ActionListener al = (ActionEvent e) -> { taOutput.append(txtMessage.getText() + "n"); }; ->
  12. 12. ラムダ構文の基礎 ● IDEで変換
  13. 13. ラムダ構文の基礎 ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "n"); } }; ActionListener al = (ActionEvent e) -> { taOutput.append(txtMessage.getText() + "n"); }; 引数の型も省略できる ->
  14. 14. ラムダ構文の基礎 ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "n"); } }; ActionListener al = (ActionEvent e) -> { taOutput.append(txtMessage.getText() + "n"); }; 引数がひとつならカッコが省略できる ->
  15. 15. ラムダ構文の基礎 ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "n"); } }; ActionListener al = (ActionEvent e) -> { taOutput.append(txtMessage.getText() + "n"); }; 本文が一行ならカッコとセミコロンが省略できる (return文のときはreturnも省略) ->
  16. 16. ラムダ構文の基礎 ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "n"); } }; ActionListener al = e ->taOutput.append(txtMessage.getText() + "n"); ->
  17. 17. メソッド参照 ● 引数ひとつのインスタンスメソッド – ● インスタンス::メソッド 引数ひとつのstaticメソッド 引数なしのインスタンスメソッド – クラス::メソッド public void init() { btnInput.addActionListener(this::inputClicked); } public void inputClicked(ActionEvent ae){ taOutput.append(txtMessage.getText() + "n"); }
  18. 18. デフォルトメソッド ● Listなどへのラムダ対応が必要 ● interfaceがメソッドをもてる ● ついでに多重継承もできる @FunctionalInterface interface Hoge{ String foo(); default void bar(){ System.out.println(foo() + "ですってバー"); } } void proc(){ Hoge h = () -> "やあ"; h.bar(); }
  19. 19. Stream ● 外部イテレーションから内部イテレーションへ – 外部イテレーション(for) – 内部イテレーション(Stream)
  20. 20. Stream 外部イテレーション for(String s : strs){ if(!s.startsWith("h")){ continue; } String u = s.toUpperCase(); System.out.println(u); } 内部イテレーション strs.stream() .filter(s -> s.startsWith("h")) .map(s -> s.toUpperCase()) .forEach(System.out::println);
  21. 21. 操作種別 ● ソース – ● 中間操作 – ● 操作対象になる 他のStreamを生成する 終端操作 – 結果の実行
  22. 22. ソース ソース 並列性 特徴 ArrayList、配列 良い SIZED, ORDERED LinkedList 悪い SIZED, ORDERED HashSet まあまあ SIZED, DISTINCT TreeSet まあまあ SIZED, DISTINCT, SORTED, ORDERED IntStream.range 良い SIZED, DISTINCT, SORTED, ORDERED BufferedReader.lines 悪い ORDERED 特徴 説明 SIZED サイズが決まっている DISTINCT 重複なし ORDERED 順番つき SORTED 整列済
  23. 23. 中間操作 操作 効果 追記 fiter() SIZEDがはずれる map() DISTINCT, SORTEDがはずれる sorted() SORTED, ORDEREDが追加 SORTEDならなにもしない distinct() DISTINCTが追加 DISTINCTならなにもしない limit() すべてそのまま
  24. 24. 終端操作 集計 toArray reduce collect sum,min,max,count anyMatch, allMatch イテレーション forEach 検索 findFirst findAny
  25. 25. プログラムモデルの変化 ● ● ● ● ● ● リダクション(畳み込み) 並列処理 遅延実行 無限ストリーム メモ化(実行結果キャッシュ) null排除
  26. 26. リダクション(畳み込み) ● ストリームの値をひとつにまとめる 3 5 3 8 6 1 3 9 38 int s = IntStream.of(3, 5, 3, 8, 6, 1, 3, 9) .sum();
  27. 27. リダクション:集計 int[] ar = {3, 5, 3, 8, 6, 1, 3, 9}; int total = 0; for(int i : ar){ total += i; } System.out.println(total); int[] ar = {3, 5, 3, 8, 6, 1, 3, 9}; System.out.println(Arrays.stream(ar).sum());
  28. 28. リダクションリスト変換 List<Integer> al = Arrays.asList(3, 5, 3, 8, 6, 1, 3, 9); List<Integer> pows = new ArrayList<>(); for(int i : al){ if(i >= 5){ continue; } int pow = i * i; pows.add(pow); } List<Integer> al = Arrays.asList(3, 5, 3, 8, 6, 1, 3, 9); List<Integer> pows = al.stream() .filter(i -> i < 5) .map(i -> i * i) .collect(Collectors.toList());
  29. 29. リダクション:判定 List<Integer> al = Arrays.asList(3, 5, 3, 8, 6, 1, 3, 9); boolean flag = true; for(int i : al){ if(i >= 10){ flag = false; } } List<Integer> al = Arrays.asList(3, 5, 3, 8, 6, 1, 3, 9); boolean flag = al.stream() .allMatch(i -> i < 10);
  30. 30. リダクション:グループ化 List<String> strs = Arrays.asList("hello", "paul", "heaven", "tatsuro"); Map<String, List<String>> result = new HashMap<>(); for(String s : strs){ String head = s.substring(0, 1); List<String> list = result.get(head); if(list == null){ list = new ArrayList<>(); result.put(head, list); } list.add(s); } for(Map.Entry<String, List<String>> me : result.entrySet()){ System.out.println(me.getKey() + ":" + me.getValue()); } List<String> strs = Arrays.asList("hello", "paul", "heaven", "tatsuro"); Map<String, List<String>> result = strs.stream() .collect(Collectors.groupingBy(s -> s.substring(0, 1))); result.forEach((k, v) -> System.out.println(k + ": " + v));
  31. 31. リダクション:reduce ● 例:合計 – (((0 + a1) + a2) + a3) – .sum() ⇒.reduce(0, (s, e) -> s + e) – .count() ⇒.map(e -> 1).sum()
  32. 32. リダクション:reduce ● reduce(単位元, (s, e) -> s * e) – – ● s * e:結合則のある2項演算 結合則:(s1 * s2) * s3=s1 * (s2 * s3) 単位元:zを単位元とすると、 s*z=z*s=s 例: – a + b[単位元0] – a × b[単位元1] – a and b[単位元 true] – list.add(b)[単位元 new ArrayList()]
  33. 33. リダクション(畳み込み)で何が変わるか ● ● ● 中間状態の隠蔽 中間状態の管理が不要になる 畳み込み操作でのバグは中間状態の操作ミスが 多かった
  34. 34. 並列処理 ● stream()の代わりにparallelStream()にするだけ – もしくはparallel()の呼び出し 3 5 3 38 8 8 6 38 11 1 3 38 7 9 12 38 19 38 19 38 int s = IntStream.of(3, 5, 3, 8, 6, 1, 3, 9) .parallel() .sum();
  35. 35. 遅延実行 ● ● ● Javaは即時評価の言語 メソッド内で使われない値でも引数に渡すときに計 算が必要だった ほとんどの場合に表示されないログやエラーでの メッセージ生成が無駄 logger.info(objWithManyFields.toString()); Objects.requireNonNull(objWithManyFields.getHoge(), objWithManyFields.toString());
  36. 36. 遅延実行 ● ● ラムダ式を渡すことで、必要なときに式を評価 LoggerやObjects.requireNonNullは対応 logger.info(() -> objWithManyFields.toString()); Objects.requireNonNull(objWithManyFields.getHoge(), () -> objWithManyFields.toString());
  37. 37. 遅延実行:Stream Pipeline ● Streamの中間操作は、終端操作のときに実行され る
  38. 38. 無限ストリーム
  39. 39. 無限ストリーム(リスト) ● Hondaストリームの無限チューンではありません ● 終端の決まらないストリーム 初期値 次の値を求める処理 IntStream.iterate(0, i -> i + 1) .limit(10) .forEach(System.out::println); IntStream.iterate(321, i -> (i * 211 + 2111) % 1999) .limit(10) .forEach(System.out::println);
  40. 40. メモ化 ● ● 実行結果キャッシュ ex:再帰フィボナッチ public static void main(String... args){ LongStream.iterate(1, i -> i + 1) .map(i -> fib(i)) .forEach(System.out::println); } public static long fib(long x){ if(x <= 2) return 1; return fib(x - 1) + fib(x - 2); }
  41. 41. メモ化:再帰フィボナッチ ● 1回ごとに2回の呼び出し – ● O(2^n) なかなか進まない! run: 1 1 2 3 5 8 13 ・ ・ ・ 1836311903 2971215073 4807526976←このあたりで止まる
  42. 42. メモ化:メモ化再帰フィボナッチ ● ● ● 同じパラメータで何度も呼び出される 結果キャッシュ⇒メモ化! computeIfAbsentが便利 static Map<Long, Long> cache = new HashMap<>(); public static long fib(long i){ return cache.computeIfAbsent(i, x -> { if(x <= 2) return 1L; return fib(x - 1) + fib(x - 2); }); } あっという間にlongも桁あふれ -415292901391291839 -7413871255405604094 -7829164156796895933 3203708661507051589 -4625455495289844344
  43. 43. null排除 ● みんな大好きNullPointerException! static String foo(int x){ return x > 0 && x < 10 ? IntStream.range(0, x) .mapToObj(c -> (char)('a' + c) + "") .collect(Collectors.joining()) : null; } static String bar(String s){ return s.chars().max().orElse(0) % 2 == 0 ? null : s; } public static void main(String... args){ for(int i = 0; i < 20; ++i){ String s = bar(foo(i)).toUpperCase(); System.out.println(s); } } nullを返す可能性 nullを返す可能性 NullPointerException の可能性
  44. 44. null排除:確実なnullチェックは面倒 for(int i = 0; i < 20; ++i){ String s = bar(foo(i)).toUpperCase(); System.out.println(s); } IntStream.range(0, 20) .mapToObj(MyClass::foo) .map(MyClass::bar) .map(String::toUpperCase) .forEach(System.out::println); 要nullチェック 呼び出しの分 解も必要 Streamでも要 nullチェック for(int i = 0; i < 20; ++i){ String s = foo(i); if(s == null) continue; String s2 = bar(s); if(s2 == null) continue; String up = s2.toUpperCase(); System.out.println(up); } IntStream.range(0, 20) .mapToObj(MyClass::foo) .filter(Objects::nonNull) .map(MyClass::bar) .filter(Objects::nonNull) .map(String::toUpperCase) .forEach(System.out::println);
  45. 45. null排除:Optional ● ● 型としてnullの可能性を示せる nullへの対処が 書きやすい Optionalが返る 一度Optionalにくるまれると nullが返っても気にならない static Optional<String> foo(int x){ return x > 0 && x < 10 ? Optional.of(IntStream.range(0, x) .mapToObj(c -> (char)('a' + c) + "") .collect(Collectors.joining())) : Optional.empty(); } static String bar(String s){ return s.chars().max().orElse(0) % 2 == 0 ? null : s; } public static void main(String... args){ for(int i = 0; i < 20; ++i){ foo(i) .map(MyClass::bar) .map(String::toUpperCase) .ifPresent(System.out::println); } }
  46. 46. null排除:Optional#flatMap static Optional<String> foo(int x){ return x > 0 && x < 10 ? Optional.of(IntStream.range(0, x) .mapToObj(c -> (char)('a' + c) + "") .collect(Collectors.joining())) : Optional.empty(); } static Optional<String> bar(String s){ return s.chars().max().orElse(0) % 2 == 0 ? Optional.empty() : Optional.of(s); } public static void main(String... args){ for(int i = 0; i < 20; ++i){ foo(i) .flatMap(MyClass::bar) .map(String::toUpperCase) .ifPresent(System.out::println); } } Optionalが返るものを はさむならflagMap
  47. 47. null排除:Streamだとちょっと面倒かも IntStream.range(0, 20) .mapToObj(MyClass::foo) .forEach(op -> { op.flatMap(MyClass::bar) .map(String::toUpperCase) .ifPresent(System.out::println); });
  48. 48. null排除:完璧ではない ● Optional自体がnullだとNullPointerException static Optional<String> foo(int x){ return x > 0 && x < 10 ? Optional.of(IntStream.range(0, x) .mapToObj(c -> (char)('a' + c) + "") .collect(Collectors.joining())) : null; } static Optional<String> bar(String s){ return s.chars().max().orElse(0) % 2 == 0 ? null : Optional.of(s); } public static void main(String... args){ for(int i = 0; i < 20; ++i){ foo(i) .flatMap(MyClass::bar) .map(String::toUpperCase) .ifPresent(System.out::println); } } nullを返してしまっている NullPointerException!

×