java se 8 lambdaで変わる プログラミングスタイル
DESCRIPTION
福岡JavaOne2013報告会第2弾でのプレゼン ラムダ構文の文法よりも、その使い方とプログラミングスタイルについてを主にまとめました。TRANSCRIPT
![Page 1: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/1.jpg)
Java SE 8 lambdaで変わるプログラミングスタイル
2013/11/15 きしだなおき
![Page 2: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/2.jpg)
ラムダがきたよ
● 匿名関数● 関数型スタイルには必須● 流行!
![Page 3: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/3.jpg)
JavaOneでのラムダ
● 関連セッションが大人気!!● どのセッションも行列!!● 人数オーバーで入れない!!
![Page 4: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/4.jpg)
ラムダの目的
● 建前:並列化
● 本音:Cool!!
![Page 5: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/5.jpg)
ラムダ構文
● 関数型インタフェース● ラムダ記法● メソッド参照
● interfaceのデフォルトメソッド
![Page 6: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/6.jpg)
関数型インタフェース
● 実装すべきメソッドがひとつだけのインタフェース
– Runnable● 実装すべきメソッド:run()
– ActionListener● 実装すべきメソッド:actionPerformed(ActionEvent)
● @FuncationalInterfaceで検査可能
![Page 7: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/7.jpg)
用意された関数型インタフェース
http://d.hatena.ne.jp/nowokay/20130824#1377300917
![Page 8: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/8.jpg)
ラムダ構文の基礎
ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "\n"); }};
![Page 9: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/9.jpg)
ラムダ構文の基礎
ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "\n"); }};
インタフェース名やメソッド名は推論される
![Page 10: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/10.jpg)
ラムダ構文の基礎
ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "\n"); }};
->
引数とメソッド本体の間に「->」が入る
![Page 11: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/11.jpg)
ラムダ構文の基礎
ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "\n"); }};
ActionListener al = (ActionEvent e) -> { taOutput.append(txtMessage.getText() + "\n");};
->
![Page 12: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/12.jpg)
ラムダ構文の基礎
● IDEで変換
![Page 13: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/13.jpg)
ラムダ構文の基礎
ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "\n"); }};
ActionListener al = (ActionEvent e) -> { taOutput.append(txtMessage.getText() + "\n");};
->
引数の型も省略できる
![Page 14: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/14.jpg)
ラムダ構文の基礎
ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "\n"); }};
ActionListener al = (ActionEvent e) -> { taOutput.append(txtMessage.getText() + "\n");};
->
引数がひとつならカッコが省略できる
![Page 15: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/15.jpg)
ラムダ構文の基礎
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も省略)
![Page 16: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/16.jpg)
ラムダ構文の基礎
ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { taOutput.append(txtMessage.getText() + "\n"); }};
ActionListener al = e ->taOutput.append(txtMessage.getText() + "\n");
->
![Page 17: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/17.jpg)
メソッド参照
● 引数ひとつのインスタンスメソッド
– インスタンス::メソッド
● 引数ひとつのstaticメソッド引数なしのインスタンスメソッド
– クラス::メソッド
public void init() { btnInput.addActionListener(this::inputClicked);}
public void inputClicked(ActionEvent ae){ taOutput.append(txtMessage.getText() + "\n");}
![Page 18: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/18.jpg)
デフォルトメソッド
● Listなどへのラムダ対応が必要
● interfaceがメソッドをもてる
● ついでに多重継承もできる
@FunctionalInterfaceinterface Hoge{ String foo(); default void bar(){ System.out.println(foo() + "ですってバー"); }}void proc(){ Hoge h = () -> "やあ"; h.bar();}
![Page 19: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/19.jpg)
Stream
● 外部イテレーションから内部イテレーションへ
– 外部イテレーション(for)
– 内部イテレーション(Stream)
![Page 20: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/20.jpg)
Stream
strs.stream() .filter(s -> s.startsWith("h")) .map(s -> s.toUpperCase()) .forEach(System.out::println);
for(String s : strs){ if(!s.startsWith("h")){ continue; } String u = s.toUpperCase(); System.out.println(u);}
外部イテレーション
内部イテレーション
![Page 21: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/21.jpg)
操作種別
● ソース– 操作対象になる
● 中間操作
– 他のStreamを生成する
● 終端操作– 結果の実行
![Page 22: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/22.jpg)
ソースソース 並列性 特徴
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 整列済
![Page 23: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/23.jpg)
中間操作
操作 効果 追記
fiter() SIZEDがはずれる
map() DISTINCT, SORTEDがはずれる
sorted() SORTED, ORDEREDが追加 SORTEDならなにもしない
distinct() DISTINCTが追加 DISTINCTならなにもしない
limit() すべてそのまま
![Page 24: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/24.jpg)
終端操作
集計 toArray
reduce
collect
sum,min,max,count
anyMatch, allMatch
イテレーション forEach
検索 findFirst
findAny
![Page 25: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/25.jpg)
プログラムモデルの変化
● リダクション(畳み込み)● 並列処理● 遅延実行● 無限ストリーム
● メモ化(実行結果キャッシュ)● null排除
![Page 26: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/26.jpg)
リダクション(畳み込み)
● ストリームの値をひとつにまとめる
3 5 3 8 6 1 3 9
38
int s = IntStream.of(3, 5, 3, 8, 6, 1, 3, 9) .sum();
![Page 27: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/27.jpg)
リダクション:集計
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());
![Page 28: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/28.jpg)
リダクションリスト変換
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());
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);}
![Page 29: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/29.jpg)
リダクション:判定
List<Integer> al = Arrays.asList(3, 5, 3, 8, 6, 1, 3, 9);boolean flag = al.stream() .allMatch(i -> i < 10);
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; }}
![Page 30: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/30.jpg)
リダクション:グループ化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));
![Page 31: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/31.jpg)
リダクション:reduce
● 例:合計
– (((0 + a1) + a2) + a3)
– .sum() .reduce(0, (s, e) -> s + e)⇒
– .count() .map(e -> 1).sum()⇒
![Page 32: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/32.jpg)
リダクション: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()]
![Page 33: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/33.jpg)
リダクション(畳み込み)で何が変わるか
● 中間状態の隠蔽● 中間状態の管理が不要になる● 畳み込み操作でのバグは中間状態の操作ミスが
多かった
![Page 34: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/34.jpg)
並列処理
3 5 3 8 6 1 3 9
int s = IntStream.of(3, 5, 3, 8, 6, 1, 3, 9) .parallel() .sum();
19
38
38123873811388
3819
● stream()の代わりにparallelStream()にするだけ
– もしくはparallel()の呼び出し
![Page 35: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/35.jpg)
遅延実行
● Javaは即時評価の言語
● メソッド内で使われない値でも引数に渡すときに計算が必要だった
● ほとんどの場合に表示されないログやエラーでのメッセージ生成が無駄
logger.info(objWithManyFields.toString());
Objects.requireNonNull(objWithManyFields.getHoge(), objWithManyFields.toString());
![Page 36: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/36.jpg)
遅延実行
● ラムダ式を渡すことで、必要なときに式を評価
● LoggerやObjects.requireNonNullは対応
logger.info(() -> objWithManyFields.toString());
Objects.requireNonNull(objWithManyFields.getHoge(), () -> objWithManyFields.toString());
![Page 37: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/37.jpg)
遅延実行:Stream Pipeline
● Streamの中間操作は、終端操作のときに実行される
![Page 38: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/38.jpg)
無限ストリーム
![Page 39: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/39.jpg)
無限ストリーム(リスト)
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);
● Hondaストリームの無限チューンではありません
● 終端の決まらないストリーム
初期値次の値を求める処理
![Page 40: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/40.jpg)
メモ化
● 実行結果キャッシュ
● 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);}
![Page 41: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/41.jpg)
メモ化:再帰フィボナッチ
● 1回ごとに2回の呼び出し
– O(2^n)● なかなか進まない!
run:11235813・・・183631190329712150734807526976←このあたりで止まる
![Page 42: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/42.jpg)
メモ化:メモ化再帰フィボナッチ
● 同じパラメータで何度も呼び出される● 結果キャッシュ⇒メモ化!
● 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); });}
-415292901391291839-7413871255405604094-78291641567968959333203708661507051589-4625455495289844344
あっという間にlongも桁あふれ
![Page 43: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/43.jpg)
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の可能性
![Page 44: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/44.jpg)
null排除:確実な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);}
for(int i = 0; i < 20; ++i){ String s = bar(foo(i)).toUpperCase(); System.out.println(s);}
要nullチェック呼び出しの分
解も必要
IntStream.range(0, 20) .mapToObj(MyClass::foo) .filter(Objects::nonNull) .map(MyClass::bar) .filter(Objects::nonNull) .map(String::toUpperCase) .forEach(System.out::println);
IntStream.range(0, 20) .mapToObj(MyClass::foo) .map(MyClass::bar) .map(String::toUpperCase) .forEach(System.out::println);
Streamでも要nullチェック
![Page 45: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/45.jpg)
null排除:Optional
● 型としてnullの可能性を示せる
● 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); } }
Optionalが返る
一度Optionalにくるまれるとnullが返っても気にならない
![Page 46: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/46.jpg)
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
![Page 47: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/47.jpg)
null排除:Streamだとちょっと面倒かも
IntStream.range(0, 20) .mapToObj(MyClass::foo) .forEach(op -> { op.flatMap(MyClass::bar) .map(String::toUpperCase) .ifPresent(System.out::println); });
![Page 48: Java SE 8 lambdaで変わる プログラミングスタイル](https://reader034.vdocuments.site/reader034/viewer/2022050804/540dd4d08d7f72927e8b4a10/html5/thumbnails/48.jpg)
null排除:完璧ではない
● Optional自体がnullだとNullPointerExceptionstatic 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!