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

Java 8 から 21 へのバージョンアップでどう変わる?:実アプリケーションで学ぶ実装のポイント / 20231111 Key Implementation Points from Java 8 to 21

mackey0225
November 11, 2023

Java 8 から 21 へのバージョンアップでどう変わる?:実アプリケーションで学ぶ実装のポイント / 20231111 Key Implementation Points from Java 8 to 21

2023年 11月 11日 に開かれた JJUG CCC 2023 Fall での発表資料です。

コーディング・実装に焦点において、
Java 8 から 21 でどのような変更があるか、また、どの様に取り入れるかについてまとめております。

発表時間枠(20分)の都合上、ポイントを絞った資料になっております。

mackey0225

November 11, 2023
Tweet

More Decks by mackey0225

Other Decks in Programming

Transcript

  1. Java 8 から 21 へのバージョンアップでどう変わる? 目次 ❏ 自己紹介 ❏ 前提

    ❏ 取り上げる機能について ❏ 各機能について実装(Before / After) ❏ まとめ 2
  2. Java 8 から 21 へのバージョンアップでどう変わる? 5 前提 • プロダクト ◦

    Java 8 (Amazon Corretto) + Spring Boot 2.7 ▪ Corretto は当初 Elastic Beanstalk を使っていた名残 • 背景 ◦ Java 8 のサポート期間は無問題󰢏 ◦ Spring Boot 2.7 の LTS の期限が間近 ▪ Spring Boot 3系だと、Java 8 では対象外󰢃 ▪ こっちがモチベーション🔥
  3. Java 8 から 21 へのバージョンアップでどう変わる? 6 今日話すこと • Java 8

    → 21 で実装がどの様に変わるのか ◦ 焦点は 実装 に関する部分 ▪ 可読性や保守性、DDDの観点で良さそうなもの ▪ JEP でいうと specification / language を中心 ▪ Garbage Collection や Virtual Thread などは対象外 • 目新しいことはあまりないかも。。。 ◦ 「何番煎じだろうが私はまだ煎じていない!」 ◦ とはいえ、何か持って帰ってもらえるようにしているつもり󰢛
  4. Java 8 から 21 へのバージョンアップでどう変わる? 今日話すこと • 取り上げる機能 ◦ Optional

    ◦ Switch 式 ◦ Records 本当は String Template とかも取り上げたかったけど、 時間的に限界でした。。。󰢛 7
  5. Java 8 から 21 へのバージョンアップでどう変わる? Optional 9 【概要】 • Optional

    自体は Java 8 で追加 • Java 9, 10, 11 にてメソッドが追加 ◦ Java 9 ▪ ifPresentOrElse / or / stream ◦ Java 10 ▪ orElseThrow ◦ Java 11 ▪ isEmpty
  6. Java 8 から 21 へのバージョンアップでどう変わる? Optional - ifPresentOrElse【Before】 10 var

    op = repository.findById(id); if (op.isPresent()) { anotherRepository.save(AnotherEntity.createOnPresent(op.get())); } else { anotherRepository.save(AnotherEntity.createOnAbsence()); } 処理イメージとしては、 リポジトリから Optional で返ってきたときに処理が分かれる場合: - エンティティが取得できる - そのエンティティをベースに別エンティティを生成し、永続化 - エンティティが取得できない - 別のメソッドで別エンティティを生成し、永続化
  7. Java 8 から 21 へのバージョンアップでどう変わる? Optional - ifPresentOrElse【After】 11 var

    op = repository.findById(id); op.ifPresentOrElse( e -> anotherRepository.save(AnotherEntity.createOnPresent(e)), () -> anotherRepository.save(AnotherEntity.createOnAbsence()) ); if文がなくなりスッキリとしつつも、処理分岐感が薄れるので、 チームや組織の方針に合わせて使い分け・統一するのが良い
  8. Java 8 から 21 へのバージョンアップでどう変わる? Switch 式 12 【概要】 •

    Java 12,13 にて Preview ◦ JEP 325: Switch Expressions (Preview) ◦ JEP 354: Switch Expressions (Second Preview) • Java 14 にて正式追加 ◦ JEP 361: Switch Expressions
  9. Java 8 から 21 へのバージョンアップでどう変わる? Switch 式 13 【簡単な機能・良いとこ】 •

    「文」だけではなく「式」として利用可能 ◦ つまり、値を返す • Arrow label でスッキリした見た目にできる ◦ 詳細は後述のコードで説明 • 網羅性チェックもある ◦ 保守開発時の考慮漏れも防げる
  10. Java 8 から 21 へのバージョンアップでどう変わる? 14 Destination destination; switch (destinationType)

    { case NURSERY: case CORPORATION: destination = new Destination(nurseryAddress); break; case ANOTHER: destination = new Destination(anotherAddress); break; default: throw new UnsupportedOperationException("未サポートの届け先種類 ") } Switch 式【Before】 Enum(destinationType)の値によって、destination に代入する値を変える。
  11. Java 8 から 21 へのバージョンアップでどう変わる? 15 Destination destination; switch (destinationType)

    { case NURSERY: case CORPORATION: destination = new Destination(nurseryAddress); break; case ANOTHER: destination = new Destination(anotherAddress); break; default: throw new UnsupportedOperationException("未サポートの届け先種類 ") } Switch 式【Before】 󰢃 switch 内で代入するために、直前で宣言している
  12. Java 8 から 21 へのバージョンアップでどう変わる? 16 Destination destination; switch (destinationType)

    { case NURSERY: case CORPORATION: destination = new Destination(nurseryAddress); break; case ANOTHER: destination = new Destination(anotherAddress); break; default: throw new UnsupportedOperationException("未サポートの届け先種類 ") } Switch 式【Before】 󰢃 条件ごとに case の行を定義しないといけない
  13. Java 8 から 21 へのバージョンアップでどう変わる? 17 Destination destination; switch (destinationType)

    { case NURSERY: case CORPORATION: destination = new Destination(nurseryAddress); break; case ANOTHER: destination = new Destination(anotherAddress); break; default: throw new UnsupportedOperationException("未サポートの届け先種類 ") } Switch 式【Before】 󰢃 case ごとに break 文の記載
  14. Java 8 から 21 へのバージョンアップでどう変わる? 18 Destination destination = switch

    (destinationType) { case NURSERY, CORPORATION -> new Destination(nurseryAddress); case ANOTHER -> new Destination(anotherAddress); default -> throw new UnsupportedOperationException("未サポートの届け先種類 "); }; Switch 式【After】
  15. Java 8 から 21 へのバージョンアップでどう変わる? 19 Destination destination = switch

    (destinationType) { case NURSERY, CORPORATION -> new Destination(nurseryAddress); case ANOTHER -> new Destination(anotherAddress); default -> throw new UnsupportedOperationException("未サポートの届け先種類 "); }; Switch 式【After】 󰢏 宣言時に値を設定できるので、 場合によっては final 付加してイミュータブルにできる
  16. Java 8 から 21 へのバージョンアップでどう変わる? 20 Destination destination = switch

    (destinationType) { case NURSERY, CORPORATION -> new Destination(nurseryAddress); case ANOTHER -> new Destination(anotherAddress); default -> throw new UnsupportedOperationException("未サポートの届け先種類 "); }; Switch 式【After】 󰢏 case も1行にまとめられる
  17. Java 8 から 21 へのバージョンアップでどう変わる? 21 Destination destination = switch

    (destinationType) { case NURSERY, CORPORATION -> new Destination(nurseryAddress); case ANOTHER -> new Destination(anotherAddress); default -> throw new UnsupportedOperationException("未サポートの届け先種類 "); }; Switch 式【After】 󰢏 冗長な break 文が排除できた
  18. Java 8 から 21 へのバージョンアップでどう変わる? 22 Destination destination = switch

    (destinationType) { case NURSERY, CORPORATION -> new Destination(nurseryAddress); case ANOTHER -> new Destination(anotherAddress); default -> throw new UnsupportedOperationException("未サポートの届け先種類 "); }; Switch 式【After】 参考までに、Arrow labels ではなく、yield も登場 cf.https://openjdk.org/jeps/354#Yielding-a-value (適材適所が前提ですが、個人的には、Arrow labels 使いがちです)
  19. Java 8 から 21 へのバージョンアップでどう変わる? Pattern Matching for switch 24

    【概要】 • Java 17〜20 にて Preview ◦ JEP 406: Pattern Matching for switch (Preview) ◦ JEP 420: Pattern Matching for switch (Second Preview) ◦ JEP 427: Pattern Matching for switch (Third Preview) ◦ JEP 433: Pattern Matching for switch (Fourth Preview) • Java 21 にて正式追加 ◦ JEP 441: Pattern Matching for switch 何回、Bボタン 押されてんねん!
  20. Java 8 から 21 へのバージョンアップでどう変わる? 25 Pattern Matching for switch【Before】

    if (item.getType() == null) { return item.getName(); } return switch (item.getType()) { case DIAPER, BABY_WIPES -> .... case APRON -> .... default -> throw new UnsupportedOperationException("未サポートの商品種別 ") };
  21. Java 8 から 21 へのバージョンアップでどう変わる? 26 if (item.getType() == null)

    { return item.getName(); } return switch (item.getType()) { case DIAPER, BABY_WIPES -> .... case APRON -> .... default -> throw new UnsupportedOperationException("未サポートの商品種別 ") }; Pattern Matching for switch【Before】 󰢃 NPE を回避するために、直前でチェックしている
  22. Java 8 から 21 へのバージョンアップでどう変わる? 27 Pattern Matching for switch【After】

    return switch (item.getType()) { case null -> item.getName() case DIAPER, BABY_WIPES -> .... case APRON -> .... default -> throw new UnsupportedOperationException("未サポートの商品種別 ") };
  23. Java 8 から 21 へのバージョンアップでどう変わる? 28 return switch (item.getType()) {

    case null -> item.getName() case DIAPER, BABY_WIPES -> .... case APRON -> .... default -> throw new UnsupportedOperationException("未サポートの商品種別 ") }; Pattern Matching for switch【After】 󰢏 NULL もラベルとして使えるので、 NPE を回避するための処理が不要。
  24. Java 8 から 21 へのバージョンアップでどう変わる? 29 Pattern Matching for switch【After】

    ただし、この例だと、列挙値で NULL が来るかもという設計がそもそも怪しいかも。。。🤨 return switch (item.getType()) { case null -> item.getName() case DIAPER, BABY_WIPES -> .... case APRON -> .... default -> throw new UnsupportedOperationException("未サポートの商品種別 ") }; 使えるから飛びつくのではなく、別の箇所や設計を見直すことは常日頃から重要!
  25. Java 8 から 21 へのバージョンアップでどう変わる? Records 30 【概要】 • Java

    14,15 にて Preview ◦ JEP 359: Records (Preview) ◦ JEP 384: Records (Second Preview) • Java 16 にて正式追加 ◦ JEP 395: Records
  26. Java 8 から 21 へのバージョンアップでどう変わる? Records 31 【簡単な機能・良いとこ】 • 手軽にイミュータブルなクラスが作成できる

    ◦ 記述量が少なく利用できる ◦ コンストラクタや equals などの定義が不要 ▪ 独自の仕様・実装をオーバーライドも可能 ◦ フィールドは常に private final が付与
  27. Java 8 から 21 へのバージョンアップでどう変わる? Records - イメージ 32 class

    Point { private final int x; private final int y; Point(int x, int y) { this.x = x; this.y = y; } int x() { return x; } int y() { return y; } public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point other = (Point) o; return other.x == x && other.y == y; } public int hashCode() { return Objects.hash(x, y); } public String toString() { return String.format("Point[x=%d, y=%d]", x, y); } } record Point(int x, int y) { } 少ない記述量でイミュータブルなクラスがで きる。 ※参照のアクセサメソッドは"get"始まりで はない
  28. Java 8 から 21 へのバージョンアップでどう変わる? Records - Value Object、DTO 33

    イミュータブルである性質を使って、DDDの文脈で • Value Object • DTO を定義するのに活かせる。 ※図表は、松岡幸一郎(@little_hand_s).ドメイン駆動設計 モデリング/実装ガ イド.p.79 図 7.1 より https://booth.pm/ja/items/1835632
  29. Java 8 から 21 へのバージョンアップでどう変わる? 34 Records - 例:分数(from JEP

    395) record Rational(int num, int denom) { public Rational { int gcd = gcd(num, denom); num /= gcd; denom /= gcd; } private int gcd(int i, int j) { 最大公約数を求める } ... 続く
  30. Java 8 から 21 へのバージョンアップでどう変わる? 35 record Rational(int num, int

    denom) { public Rational { int gcd = gcd(num, denom); num /= gcd; denom /= gcd; } private int gcd(int i, int j) { 最大公約数を求める } ... 続く Records - 例:分数(from JEP 395) コンストラクタを上書き。 入力値をそのままではなく、通分して生成する。 これで、分数としてのルール(=ドメイン知識)を保てる。
  31. Java 8 から 21 へのバージョンアップでどう変わる? 37 record Rational(int num, int

    denom) { public Rational { int gcd = gcd(num, denom); num /= gcd; denom /= gcd; } private int gcd(int i, int j) { 最大公約数を求める } ... 続く Records - 例:分数(from JEP 395) // 普通のコンストラクタ public Rational(int num, int denom) { int gcd = gcd(num, denom); num /= gcd; denom /= gcd; this.num = num; this.denom = denom; } 󰢏 コンストラクタも簡略化できる! ただし、レコードのヘッダーと一致する場合に限る。
  32. Java 8 から 21 へのバージョンアップでどう変わる? 38 ... 続き public Rational

    add(Rational r) { return new Rational( this.num * r.denom + r.num * this.denom, this.denom * r.denom ); } public Rational multiply(Rational r) { return new Rational( this.num * r.num, this.denom * r.denom ); } Records - 例:分数(from JEP 395)
  33. Java 8 から 21 へのバージョンアップでどう変わる? 39 ... 続き public Rational

    add(Rational r) { return new Rational( this.num * r.denom + r.num * this.denom, this.denom * r.denom ); } public Rational multiply(Rational r) { return new Rational( this.num * r.num, this.denom * r.denom ); } 足し算の際に新しいインスタンスを生成する。 分数のルールを呼び出し側に漏れ出さないように定義できる。 ドメインルールの流出を防げる Records - 例:分数(from JEP 395)
  34. Java 8 から 21 へのバージョンアップでどう変わる? まとめ 41 • 進化を取り入れて、サービス利用者の方々に還元できるかも。。。? ◦

    冗長なコードや記述量が減ったり、 ◦ 可読性が良くなったり、 ◦ 保守性が高くなったり、 • とはいえ、 ◦ サービス開発においては組織での意思決定・取捨選択は必要 ◦ コーディングルールの策定やバージョンアップ戦略も必要 • なので、日頃からの情報のアップデートは必要!
  35. Java 8 から 21 へのバージョンアップでどう変わる? 補足 - IntelliJ IDEA の

    コードインスペクション機能 43 インスペクション内に「Java 言語レベルの移行支援」がある プロジェクト内で適用できそうな箇所をサジェストしてくれる