Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Java8の新機能/Java8 New Feature

Java8の新機能/Java8 New Feature

Java8の新機能紹介(ラムダ式/メソッド参照/Stream/Optional)

Toshiyuki Imaizumi

June 09, 2021
Tweet

More Decks by Toshiyuki Imaizumi

Other Decks in Programming

Transcript

  1. 目次 •はじめに •ラムダ式 ◦ ラムダ式について ◦ 関数型インタフェース ◦ メソッド参照 •Stream

    ◦ Streamについて ◦ 中間操作 ◦ 終端操作 •Optional •その他 •まとめ 2
  2. ラムダ式について(1/4) •従来匿名クラスを使っていた部分をより簡潔に書ける構文 •例として、じゃんけんゲームで、プレイヤーの出す手をStrategyパ ターンで実現する場合を考える public class Player{ private Strategy strategy;

    public Player(String name,Strategy strategy) { … } public Hand getNextHand() { return strategy.getHand(); } } プレイヤークラス public interface Strategy{ Hand getHand(); } public enum Hand{ ROCK,SCISSORS,PAPER } Strategyインタフェースの実装 5
  3. ラムダ式について(2/4) •ラムダ式の例として、常にグーを出すStrategyを匿名クラスと比較する •赤字部分が同一の意味を持つコードとなる。 ラムダ式の方が圧倒的にシンプル Player guPlayer = new Player("CPU1", new

    Strategy() { @Override public Hand getHand() { return Hand.ROCK; } }); 匿名クラスを利用する従来のコード Player guPlayer = new Player("CPU1", () -> Hand.ROCK); ラムダ式を利用するコード 6
  4. ラムダ式について(3/4) •ラムダ式は匿名クラスで自明である以下の部分を省略したもの ◦ クラス名:new Strategy ◦ メソッドのシグネチャ:getHand ◦ シグネチャ: メソッドの名前、返り値、引数の型

    •メソッドが1文の場合、{}とreturnも省略可能 Player guPlayer = new Player("CPU1", new Strategy() { @Override public Hand getHand() { return Hand.ROCK; } }); 匿名クラスを利用する従来のコード(再掲) Player guPlayer = new Player("CPU1", () -> Hand.ROCK); ラムダ式を利用するコード(再掲) 7
  5. ラムダ式について(4/4) •ラムダ式で実装するメソッドが引数ありの場合の例、引数の型も省 略可能。引数が1つなら引数の()も省略可能 ◦ グーとパーを交互に出すStrategyの例 Player guPaPlayer = new Player("CPU1",

    h -> h == Hand.PAPER ? Hand.ROCK : Hand.PAPER); //引数の型、括弧、return省略 Player guPaPlayer2 = new Player("CPU2", (Hand p) -> {return p == Hand.PAPER ? Hand.ROCK : Hand.PAPER;}); //引数の型、括弧、return省略なし ラムダ式を利用するコード 8 public interface Strategy{ Hand getHand(Hand prevHand); } Strategyインタフェース
  6. メソッド参照(1/3) •関数型インタフェースに対して、ラムダ式以外に、メソッドそのもの (メソッド参照)を渡すことができる ◦ クラスメソッドなら「クラス名::メソッド名」 インスタンスメソッドなら「変数名::メソッド名」 メソッド参照で使うクラス public class Hoge{

    public static Hand getRock() {return Hand.ROCK;} public Hoge(Hand hand){this.hand = hand;} private Hand hand; public Hand getHand() {return hand;} } 10 // クラスメソッドのメソッド参照 Player guPlayer = new Player("CPU1", Hoge::getRock); // インスタンスのメソッド参照 Hoge hoge = new Hoge(Hand.ROCK); Player guPlayer2 = new Player("CPU2", hoge::getHand); メソッド参照を利用するコード
  7. メソッド参照(2/3) •関数型インタフェースのメソッドの第一引数の型と、 メソッド参照で渡すクラスの型が一致している場合、 第一引数のインスタンスの「メソッド参照で渡したメソッド」が呼ばれる 11 @FunctionalInterface public interface Operator{ BigDecimal

    operate(BigDecimal b1, BigDecimal b2); } public class Piyo{ public BigDecimal calc(Operator op){ return op.operate(BigDecimal.ONE, BigDecimal.TEN); } } BigDecimal#addのシグネチャは BigDecimal add(BigDecimal)。 BigDecimal operate(BigDecimal,BigDecimal) とは引数が異なるが、第一引数の型と一致す るので渡せる。b1.add(b2) と同じ意味になる メソッド参照を渡すインタフェース Piyo piyo = new Piyo(); // ラムダ式の例 piyo.calc ((b1,b2) -> b1.add(b2)); // 異なるシグネチャのメソッド参照の例 // クラス名::メソッド名だが、クラスメソッドではない piyo.calc (BigDecimal::add); メソッド参照を利用するコード
  8. メソッド参照(3/3) •前頁と同様の例(Stringをその文字長に変換) 12 @FunctionalInterface public interface Operator{ int operate(String s);

    } public class Piyo{ public int operate(Operator op){ return op.operate(“fuga”); } } String#lengthのシグネチャは int length()。 int operate(String s) とは引数が異なるが、第一引数の型と一致す るので渡せる。s.length() と同じ意味になる メソッド参照を渡すインタフェース Piyo piyo = new Piyo(); // ラムダ式の例 piyo.operate( (s) ->s.length()); // 異なるシグネチャのメソッド参照の例 piyo.operate( String::length ); メソッド参照を利用するコード
  9. 既存の関数型インタフェース(1/4) •Consumer ◦ 用意されているメソッド:void accept(T t) ◦ 使用例:リストに対して何らかの処理をさせる private void

    consume(Consumer<Model> consumer) { for (Model model : models) { consumer.accept(model); } } private void consumeSample() { consume((m) -> System.out.println(m)); consume((m) -> System.out.println(m.getValue1())); } 13
  10. 既存の関数型インタフェース(3/4) •Function ◦ 用意されているメソッド:R apply(T t); ◦ 使用例:リストから任意のフィールドの値を集計する private BigDecimal

    sum(Function<Model, BigDecimal> extractor) { BigDecimal sum = BigDecimal.ZERO; for (Model model : models) { sum = sum.add(extractor.apply(model)); } return sum; } private void functionSample() { System.out.println( sum((m) -> m.getAmt1())); System.out.println( sum((m) -> m.getAmt2())); } 15
  11. 既存の関数型インタフェース(4/4) •Predicate ◦ 用意されているメソッド:boolean test(T t) ◦ boolean専用のSupplier •UnaryOperator ◦

    用意されているメソッド:T apply(T t) ◦ 引数と返り値の型が同じFunction •BiFunction ◦ 用意されているメソッド:R apply(T t, U u) ◦ 引数が二つあるFunction •BinaryOperator ◦ 用意されているメソッド:T apply(T t, U u) ◦ 戻り値と返り値が同じBiFunction •使用例は後述のStreamで 16
  12. Streamについて(2/4) •リストの中から特定の条件を満たす要素の合計値を取得する例 BigDecimal sum = BigDecimal.ZERO; for(Model model : models){

    if(!model.isInvalid()){ if(model.getAmt1() != null) { sum = sum.add(model.getAmt1()); } } } 従来のコード BigDecimal sum = models.stream() .filter((e)->!e.isInvalid()) //無効を除外 .map(Model::getAmt1) //指定のフィールドを取得 .filter(Objects::nonNull) //nullを除外 .reduce(BigDecimal.ZERO, BigDecimal::add); //要素を加算 Streamを使ったコード 19 public class Model{ boolean isInvalid(){...}; BigDecimal getAmt1(){...}; } 集計に使うモデルクラス
  13. Streamについて(3/4) •前頁のStreamに対する操作を見てみる •中間操作 ◦ filter(Predicate) ◦ falseが返された要素を除外する ◦ map(Function) ◦

    要素を変換する。前頁の例だと、BigDecimal apply(Model)となるメソッドを渡している •終端操作 ◦ reduce(T identity, BinaryOperator<T> accumulator) ◦ 第1引数:初期値 ◦ 第2引数:要素の処理内容。T apply(T t, U u) が今回だと BigDecimal apply (BigDecimal t, BigDecimal u) となり、BigDecimal#add(BigDecimal)をメソッド参照で渡し ているため、t.add(u)が呼ばれることになる 20
  14. 中間操作 •distinct():重複する要素を削除する •sorted(Comparator):要素をソートする ◦ Java8でのComparatorの実装については後述 •limit(long maxSize):指定サイズまで要素を減らす(先頭から取得) •skip(long n):先頭から指定数の要素を削除する •flatMap(Function<T,

    Stream<R>>):各要素をStreamに変換し、それ らをまとめたStreamを作る。1:nの変換をしたい場合に利用 (mapは1:1の変換のため) ◦ 例:文字列をカンマで分割する List<String> csvList = Arrays.asList("a,b,c","a,c,d", "1,2,3"); csvList.stream().flatMap(s -> Arrays.stream(s.split(","))).forEach(System.out::println); 22
  15. 終端操作(2/2) •void forEach(Consumer):各要素に対して指定の処理を行う ◦ 例:models.stream().forEach(System.out::println); •R collect(Collector):要素を変換する。引数のCollectorはCollectors のクラスメソッドで作成する ◦ Listにするのであれば、collect(Collectors.toList())

    ◦ Mapにするのであれば、toMap(Function keyMapper,Function ValueMapper) かgroupingBy(Function classifier) ◦ Modelのvalue1をキーに、amt1をバリューにしたMapを作る例 ◦ Modelのvalue1をキーに、Model自身をバリューにしたMapを作る例 Map<String,BigDecimal> map = models.stream().collect( Collectors.toMap(Model::getValue1, Model::getAmt1)); 24 Map<String,List<Model>> map = models.stream().collect( Collectors.groupingBy(Model::getValue1));
  16. Streamの利用例(1/3) •Hibernateのプロジェクションでコードと値を取得し、そのMapを作る例 25 criteria.setResultTransformer( CriteriaSpecification.ALIAS_TO_ENTITY_MAP); List<Map<String, Object>> resultList = criteria.list();

    // 以下のMap作成処理をStreamに置き換える Map<String,BigDecimal> resultMap = new HashMap<>(); for ( Map<String, Object> map : resultList) { String code = ( String) map.get( “code”); BigDecimal amt = ( BigDecimal) map.get(“amt1”); resultMap.put( code, amt); } 従来のコード Map<String, BigDecimal> resultMap = resultList.stream().collect( Collectors .toMap( map -> ( String) map.get(“code”), map -> ( BigDecimal) map.get(“amt1”))); StreamとCollectorを使ったコード
  17. Streamの利用例(2/3) •複数のリストをまとめてStreamにする(正の金額のみをリストにする) 26 List<BigDecimal> amts1 = Arrays.asList(BigDecimal.valueOf(1),BigDecimal.valueOf(2)); List<BigDecimal> amts2 =

    Arrays.asList(BigDecimal.valueOf(-1),BigDecimal.valueOf(3)); List<BigDecimal> plusAmts = new ArrayList<BigDecimal>(); for (BigDecimal amt : amts1) { if(amt.signum() > 0) { plusAmts.add(amt);} } // amts2についても同様にループ 従来のコード List<BigDecimal> plusAmts = Stream.of(amts1.stream(), amts2.stream()) // Stream<Stream<BigDecimal>>が返る .flatMap(e->e) // Stream<BigDecimal>に変換 .filter(e->e.signum()>0).collect(Collectors.toList()); Streamを使ったコード
  18. Streamの利用例(3/3) •指定のキーで指定のカラムの金額を集計する 27 Map<String,BigDecimal> amtMap = new HashMap<>(); for (Model

    model : models) { BigDecimal sum = amtMap.get(model.getValue1()); if(sum == null) { sum = BigDecimal.ZERO; } amtMap.put(model.getValue1(), sum.add(model.getAmt1())); } 従来のコード Map<String, BigDecimal> amtMap = models.stream(). collect(Collectors.groupingBy(Model::getValue1, Collectors.reducing(BigDecimal.ZERO, Model::getAmt1, BigDecimal::add))); Streamを使ったコード
  19. Optionalとは(1/2) •nullを扱いやすくするためのクラス •従来では、メソッドの返り値がnullでありうる場合、呼び出し元で nullチェックが必要だった。かつ、nullチェックを忘れた場合、 実行時にNPEが発生するまで気づけないという問題があった。 29 public void hoge(){ Model

    model = getModel(); if(model != null){ System.out.println(getModel().getAmt1()); } } nullチェックを忘れた不具合のあるコード public void hoge(){ Model model = getModel(); // ここでmodelがnullだとNPE System.out.println(model.getAmt1()); } nullチェックをしている適切なコード
  20. Optionalのメソッド(1/5) •void ifPresentOrElse(Consumer,Runnable) ◦ 保持する値がnullでない場合はConsumerを、nullの場合はRunnableを実行 ◦ ※このメソッドはJava8ではなくJava9での追加 31 従来のコード Model

    model = getModel(); if(model != null){ System.out.println(model.getAmt1()); } else{ System.out.println(“empty”); } Optionalを使ったコード Optional<Model> model = getModel(); model.ifPresentOrElse( (m) -> System.out.println(m.getAmt1()), () -> System.out.println("empty") );
  21. Optionalのメソッド(2/5) •T orElse(T) ◦ 保持する値がnullでない場合はそれを返す。nullの場合は引数の値を返す 32 従来のコード //getAmt1の返り値の型はBigDecimal BigDecimal amt

    = getAmt1(); BigDecimal ret; if(amt!= null){ ret= amt; } else{ ret = BigDecimal.ZERO; } return ret; Optionalを使ったコード // getAmt1の返り値の型はOptional<BigDecimal> Optional<BigDecimal> amt = getAmt1() BigDecimal ret = amt.orElse(BigDecimal.ZERO); return ret;
  22. Optionalのメソッド(3/5) •Optional<U> map(Function<T, U>) ◦ 保持する値がnullでなければ、変換した結果をOptionalで包んで返す。nullなら nullを保持するOptionalを返す 33 従来のコード BigDecimal

    ret; if(model != null){ BigDecimal amt = model.getAmt1(); if(amt != null){ ret = model.getAmt1(); } else{ ret = BigDecimal.ZERO; } } else{ ret= BigDecimal.ZERO; } Optionalを使ったコード // modelの型はOptional<Model> // getAmt1の返り値の型はBigDecimal Optional<Model> model = getModel(); Optional<BigDecimal> amt = model.map(Model::getAmt1); ret = amt.orElse(BigDecimal.ZERO); model.mapの結果、modelがnullでない場合は model.getAmt1の値を持つOptionalが返る。 modelがnullの場合はnullを持つOptionalを返す。
  23. Optionalのメソッド(4/5) •Optional<U> flatMap(Function<T, Optional<U>>) ◦ 保持する値がnullでない場合は変換結果をOptionalに包まず返すmap 34 flatMapを使わないコード flatMapを使ったコード Optional<Model>

    model = getModel(); Optional<BigDecimal> amt = model.flatMap(Model::getAmt1); ret = amt.orElse(BigDecimal.ZERO); // modelの型はOptional<Model> // getAmt1の返り値の型はOptional<BigDecimal> Optional<Model> model = getModel(); // XXX mapだとOptionalが入れ子になってしまう Optional<Optional<BigDecimal>> amtOpt = model.map(Model::getAmt1); // Optional#getは保持する値を返すメソッド Optional<BigDecimal> amt = amtOpt.get(); ret = amt.orElse(BigDecimal.ZERO);