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

Java8の新機能/Java8 New Feature

Java8の新機能/Java8 New Feature

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

D975a107f12d162c7017e477cfe07591?s=128

Toshiyuki Imaizumi

June 09, 2021
Tweet

Transcript

  1. Java8の新機能紹介 ラムダ式、Streamを中心に 2021/6/10 今泉俊幸 1

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

    ◦ Streamについて ◦ 中間操作 ◦ 終端操作 •Optional •その他 •まとめ 2
  3. はじめに •なぜ今Java8の新機能を学ぶのか ◦ Java8のリリースは2014/3。現在日(2021/6)時点での最新はJava16 •Java8はラムダ式、メソッド参照という構文の追加があり、 従来の知識では読み解けないコードが増えた。 •Stream APIとOptionalクラスの追加により、保守性の高いコードが 書けるようになったが、これらもラムダ式、メソッド参照を使う •Java8で追加された構文やクラスを知り、よりよいコードを書けるよう

    になることを目的とする 3
  4. ラムダ式 4

  5. ラムダ式について(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
  6. ラムダ式について(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
  7. ラムダ式について(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
  8. ラムダ式について(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インタフェース
  9. 関数型インタフェース •ラムダ式が使えるのは「関数型インタフェース」に限られる •関数型インタフェース:抽象メソッドを1つだけ持つインタフェース ◦ 定数は持っていてもOK ◦ 関数型インタフェースを表すアノテーションとして、「@FunctionalInterface」がある ◦ アノテーションをつけたインタフェースが関数型インタフェースでなくなった場合 (メソッドを追加した場合など)はコンパイルエラーとしてくれる(なので付けよう)

    ◦ 関数型インタフェースでないものにラムダ式を使おうとした場合もコンパイルエラーとなる @FunctionalInterface public interface Strategy{ Hand getHand(); } 9
  10. メソッド参照(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); メソッド参照を利用するコード
  11. メソッド参照(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); メソッド参照を利用するコード
  12. メソッド参照(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 ); メソッド参照を利用するコード
  13. 既存の関数型インタフェース(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
  14. 既存の関数型インタフェース(2/4) •Supplier ◦ 用意されているメソッド:T get() ◦ 使用例:値がnullの場合、何らかのデフォルトの値を返す ◦ 上記のkillNullでは、引数modelがnullではない場合、supplier.getは呼ばれな いため、不要なインスタンスが作られないというメリットがある

    private Model killNull(Model model, Supplier<Model> defaultValueSupplier) { return model != null ? model : defaultValueSupplier.get(); } private void supplierSample() { Model model = killNull(baseModel, () -> new Model("aa", "bb")); } 14
  15. 既存の関数型インタフェース(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
  16. 既存の関数型インタフェース(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
  17. Stream 17

  18. Streamについて(1/4) •ListやSetのような複数の値を操作しやすくするためのAPI •for文で値を集計したり、必要な要素のみを取得したり、といった コードが簡潔に書ける •配列やCollectionをStream型に変換し、Streamのメソッドで操作する ◦ Collectionにstreamメソッドが追加されており、これで変換できる •Streamの操作は要素をフィルタしたりソートしたりする「中間操作」と、 結果を取得する「終端操作」に分かれる ◦

    中間操作はStreamを返すため、メソッドチェインで書ける ◦ 終端操作は1つのStreamに対して1回のみ呼べる 18
  19. 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(){...}; } 集計に使うモデルクラス
  20. 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
  21. Streamについて(4/4) •reduceの動作イメージ 21 0 identity 100 300 streamの要素 1000 accumulator.apply(0,100)

    accumulator.apply(100,300) accumulator.apply(400,1000) 1400 sum
  22. 中間操作 •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
  23. 終端操作(1/2) •boolean allMatch(Predicate):要素が全てtrueを返せばtrue。1つで もfalseを返すものがあればfalse(要素0はtrue) •boolean anyMatch(Predicate):1つでもtrueを返す要素があればtrue。 trueを返すものが1つもなければfalse (要素0はfalse) •long count():要素数を返す

    •Optional findFirst:最初の要素を返す •Optional max(Comparator):最大の要素を返す •Optional min(Comparator):最小の要素を返す ◦ Optionalについては後述 23
  24. 終端操作(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));
  25. 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を使ったコード
  26. 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を使ったコード
  27. 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を使ったコード
  28. Optional 28

  29. 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チェックをしている適切なコード
  30. Optionalとは(2/2) •Optionalを使う事で、呼び出し元にnullの扱いを強制できる ◦ nullになりうる値を返す場合、Optional#ofNullable(value)を使ってOptional型で返す ◦ Optionalには、Optionalが保持する値がnullかどうかで挙動が変わるメソッドがある •void ifPresent(Consumer) ◦ Optionalが保持する値がnullでない場合はConsumerの処理を行う。nullの場合は何もしない

    30 public Optional<Model> getModel() { return Optional.ofNullable(model); } public void hoge(){ Optional<Model> model = getModel(); model.ifPresent((m) -> System.out.println(m.getAmt1())); } 前頁のコードをOptionalで書き換えた例
  31. 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") );
  32. 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;
  33. 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を返す。
  34. 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);
  35. Optionalのメソッド(5/5) •boolean isPresent:値がnullかどうかを返す •T get:値を返す。値がnullならNoSuchElementExceptionを投げる •getを使うと、事前にifPresentでnullかどうか調べないと実行時例外 が発生する。これでは、従来のコードと変わらない。getを使うのは 非推奨 35 従来のコード

    if(model != null){ System.out.println(model.getValue1()); } else{ System.out.println(“empty”); } Optionalを使ったコード if(model.isPresent()){ System.out.println(model.get().getValue1()); ) else{ System.out.println(“empty”); }
  36. Optionalの使いどころ •Java ChampionのStephen ColebourneはOptionalはAPIの境界での 返り値でのみ使うべき、と述べ、以下の指針を出している •1.インスタンスフィールドでは使わない •2.privateスコープでは使わない •3.nullになりうる値に対してのgetterでは使う •4.setterとコンストラクタでは使わない •5.nullになりうる値を返すビジネスロジックの返り値として使う

    ◦ 参考URL:https://blog.joda.org/2015/08/java-se-8-optional-pragmatic- approach.html 36
  37. その他 37

  38. インタフェースでのメソッド定義 •インタフェースでクラスメソッド、defaultメソッドを定義できるように なった •defaultメソッドは通常のメソッドと同じ。オーバーライドが出来、 オーバーライドされなければインタフェースで実装した内容で処理 ◦ 抽象メソッドが1つだけなら、クラスメソッド、defaultメソッドがあっても 関数型インタフェースとしてみなせる 38 @FunctionalInterface

    interface Strategy{ static String hoge() {return "hoge";} default String getPiyo() {return "piyo";} Hand getHand(); }
  39. Comparatorを作る便利メソッド •Comparatorインタフェースに便利なクラスメソッドが追加されている。 Listにもdefaultメソッドでsortが追加された(Collections#sortが不要) •指定の1項目でソートする:comparing(Function) ◦ models.sort(Comparator.comparing(Model::getAmt1)); •複数の項目でソートする:thenComparing(Compartor) ◦ models.sort(comparing(Model::getAmt1) .thenComparing(comparing(Model::getAmt2));

    •降順でソートする(reverseOrder) ◦ models.sort(comparing(Model::getAmt1, Comparator.reverseOrder()); •nullを考慮してソートする(nullsFirst,nullsLast) ◦ models.sort(Comparator.comparing(Model::getAmt1, Comparator.nullsFirst( Comparator.naturalOrder()))); 39
  40. まとめ •匿名クラスはラムダ式を使うと簡潔に書けるようになる •for文はStreamを使うと簡潔に書けるようになる ◦ Streamを操作する際にラムダ式、メソッド参照を使う •nullを返すメソッドはOptionalを返すことでNPEを防止できる ◦ メソッドの返り値の型以外でOptionalを使うことは非推奨 •Comparatorを作る際は自分で実装するのではなく、Comparatorに 追加されたクラスメソッドで作成するとよい

    40