Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Java 8 から 21 へのバージョンアップでどう変わる?:実アプリケーションで学ぶ実装のポ...

mackey0225
January 25, 2024

Java 8 から 21 へのバージョンアップでどう変わる?:実アプリケーションで学ぶ実装のポイント【増補改訂版】 / 20240125 Key Implementation Points from Java 8 to 21 Expanded Edition

2024年 01月 25日 に開かれた 関ジャバ'24 1月度 での発表資料です。
https://kanjava.connpass.com/event/306493/

以前、JJUG CCC 2023 Fall で発表した内容に加え、
チーム開発におけるバージョンアップ運用についてを加筆しました。

【参考】
元資料:Java 8 から 21 へのバージョンアップでどう変わる?:実アプリケーションで学ぶ実装のポイント
https://speakerdeck.com/mackey0225/20231111-key-implementation-points-from-java-8-to-21

mackey0225

January 25, 2024
Tweet

More Decks by mackey0225

Other Decks in Programming

Transcript

  1. Java 8 から 21 へのバージョンアップでどう変わる? 2 話の前に 今回は JJUG CCC

    2023 Fall の増補改訂版ですー 前半は体裁のみの変更で、内容はほぼ一緒ですー ちなみに、後半も下記の記事がベースですー https://zenn.dev/babyjob/articles/operation-in-upgrading-java-version-from-8-to-17
  2. Java 8 から 21 へのバージョンアップでどう変わる? 3 話の前に 今回は JJUG CCC

    2023 Fall の増補改訂版ですー 前半は体裁のみの変更で、内容はほぼ一緒ですー ちなみに、後半も下記の記事がベースですー https://zenn.dev/babyjob/articles/operation-in-upgrading-java-version-from-8-to-17 ご容赦ください
  3. Java 8 から 21 へのバージョンアップでどう変わる? 目次 ❏ 自己紹介 ❏ 前提

    ❏ 実装について ❏ Java 8 → 21 で「実装」がどのように変わるのか ❏ 運用について ❏ Java 8 → 17 にした「運用」をどのように進めたか ❏ まとめ 4
  4. Java 8 から 21 へのバージョンアップでどう変わる? 自己紹介 名前: 浅野 正貴 所属: BABY

    JOB 株式会社(2022-06 入社) 役割: Java 書いたり、AWS 触ったり X: @mackey0225 GitHub: @mackey0225 関ジャバで話をするのは初めてなので嬉しいです! 5
  5. Java 8 から 21 へのバージョンアップでどう変わる? 7 前提 - プロダクト •

    対応前:Java 8 (Corretto) + Spring Boot 2.7 ◦ Corretto は Elastic Beanstalk の名残り ◦ 現在は ECS on Fargate で稼働 • 対応後:Java 17 (Corretto) + Spring Boot 2.7 ◦ ひとまず、Java のみバージョンアップ ◦ 21 には上げなかった理由は、次スライド
  6. Java 8 から 21 へのバージョンアップでどう変わる? 8 前提 - 背景・きっかけ •

    Java 8 のサポート期間は無問題󰢏 • Spring Boot 2.7 の LTS の期限が間近 ◦ Spring Boot 3 系だと、Java 8 がサポート外󰢃 ◦ こっちがモチベーション🔥🔥🔥 ◦ 一旦、17 にあげて回避したかった
  7. Java 8 から 21 へのバージョンアップでどう変わる? 9 今日話すこと • Java 8

    → 21 で「実装」がどの様に変わるのか ◦ 可読性や保守性、DDDの観点でピックアップ • Java 8 → 17 にした「運用」をどのように進めたか ◦ やったことと苦労話が中心
  8. Java 8 から 21 へのバージョンアップでどう変わる? Optional - 概要 15 •

    Optional 自体は Java 8 で追加 ◦ 「null かもしれないという型表現」 • Java 9, 10, 11 にてメソッドが追加 ◦ Java 9: ifPresentOrElse / or / stream ◦ Java 10: orElseThrow ◦ Java 11: isEmpty
  9. Java 8 から 21 へのバージョンアップでどう変わる? Optional - ifPresentOrElse【Before】 16 var

    op = hogeRepository.findById(id); // 中身が取れるかどうかで、処理を分けたい if (op.isPresent()) { fugaRepository.save(FugaEntity.createOnPresent(op.get())); } else { fugaRepository.save(FugaEntity.createOnAbsence()); }
  10. Java 8 から 21 へのバージョンアップでどう変わる? Optional - ifPresentOrElse【After】 17 var

    op = hogeRepository.findById(id); op.ifPresentOrElse( e -> fugaRepository.save(FugaEntity.createOnPresent(e)), () -> fugaRepository.save(FugaEntity.createOnAbsence()) ); if 文がなくなり処理分岐感が薄れるので、 チームや組織の方針に合わせて使い分け・統一するのが良い
  11. Java 8 から 21 へのバージョンアップでどう変わる? • Java 12,13 にて Preview

    ◦ JEP 325: Switch Expressions (Preview) ◦ JEP 354: Switch Expressions (Second Preview) • Java 14 にて正式追加 ◦ JEP 361: Switch Expressions Switch Expressions - 概要 19
  12. Java 8 から 21 へのバージョンアップでどう変わる? • 「文」だけではなく「式」として利用可能 ◦ つまり、値を返す •

    Arrow label でスッキリした見た目にできる ◦ 詳細は後述のコードで説明 • 網羅性チェックもある ◦ 保守開発時の考慮漏れも防げる Switch Expressions - 特徴・良いところ 20
  13. Java 8 から 21 へのバージョンアップでどう変わる? 21 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 Expressions【Before】 Enum(destinationType)の値によって、destination に代入する値を変える。 Destination destination; switch (destinationType) { case NURSERY: case CORPORATION: destination = new Destination(nurseryAddress); break; case ANOTHER: destination = new Destination(anotherAddress); break; default: throw new UnsupportedOperationException("未サポートの種類"); }
  14. Java 8 から 21 へのバージョンアップでどう変わる? 22 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 Expressions【Before】 Enum(destinationType)の値によって、destination に代入する値を変える。 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 内で代入するために、直前で宣言している
  15. Java 8 から 21 へのバージョンアップでどう変わる? 23 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 Expressions【Before】 Enum(destinationType)の値によって、destination に代入する値を変える。 Destination destination; switch (destinationType) { case NURSERY: case CORPORATION: destination = new Destination(nurseryAddress); break; case ANOTHER: destination = new Destination(anotherAddress); break; default: throw new UnsupportedOperationException("未サポートの種類"); } 󰢃 条件ごとに case の行を定義しないといけない
  16. Java 8 から 21 へのバージョンアップでどう変わる? 24 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 Expressions【Before】 Enum(destinationType)の値によって、destination に代入する値を変える。 Destination destination; switch (destinationType) { case NURSERY: case CORPORATION: destination = new Destination(nurseryAddress); break; case ANOTHER: destination = new Destination(anotherAddress); break; default: throw new UnsupportedOperationException("未サポートの種類"); } 󰢃 case ごとに break 文の記載が必要
  17. Java 8 から 21 へのバージョンアップでどう変わる? 25 Switch Expressions【After】 Destination destination

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

    = switch (destinationType) { case NURSERY, CORPORATION -> new Destination(nurseryAddress); case ANOTHER -> new Destination(anotherAddress); default -> throw new UnsupportedOperationException("未サポ..."); }; 󰢏 宣言時に値を設定できる   場合によっては final 付加して不変にできる
  19. Java 8 から 21 へのバージョンアップでどう変わる? 27 Switch Expressions【After】 Destination destination

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

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

    = switch (destinationType) { case NURSERY, CORPORATION -> new Destination(nurseryAddress); case ANOTHER -> new Destination(anotherAddress); default -> throw new UnsupportedOperationException("未サポ..."); }; 参考までに、Arrow labels ではなく yield も登場 cf. https://openjdk.org/jeps/354#Yielding-a-value
  22. Java 8 から 21 へのバージョンアップでどう変わる? • Java 17〜20 にて Preview

    ◦ JEP 406、JEP 420、JEP 427、JEP 433 • Java 21 にて正式追加 ◦ JEP 441: Pattern Matching for switch Pattern Matching for switch - 概要 31 何回、Bボタン 押されてんねん!
  23. Java 8 から 21 へのバージョンアップでどう変わる? 32 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("未サポ...") };
  24. Java 8 から 21 へのバージョンアップでどう変わる? 33 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("未サポ...") }; 󰢃 NPE を回避するために、直前でチェックしている
  25. Java 8 から 21 へのバージョンアップでどう変わる? 34 Pattern Matching for switch【After】

    return switch (item.getType()) { case null -> item.getName() case DIAPER, BABY_WIPES -> ... case APRON -> ... default -> throw new UnsupportedOperationException("未サポ...") };
  26. Java 8 から 21 へのバージョンアップでどう変わる? 35 Pattern Matching for switch【After】

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

    return switch (item.getType()) { case null -> item.getName() case DIAPER, BABY_WIPES -> ... case APRON -> ... default -> throw new UnsupportedOperationException("未サポ...") }; 󰢏 NULL もラベルとして使える   → NPE を回避する処理が不要 でも、ちょっと待って。
  28. Java 8 から 21 へのバージョンアップでどう変わる? 37 Pattern Matching for switch【After】

    return switch (item.getType()) { case null -> item.getName() case DIAPER, BABY_WIPES -> ... case APRON -> ... default -> throw new UnsupportedOperationException("未サポ...") }; 󰢏 NULL もラベルとして使える   → NPE を回避する処理が不要 でも、ちょっと待って。 そもそも、列挙値なのに NULL が来るかも という設計が怪しいんじゃない。。。 使えるから飛びつくのではなく、 設計を見直すことも重要!
  29. Java 8 から 21 へのバージョンアップでどう変わる? • Java 14,15 にて Preview

    ◦ JEP 359: Records (Preview) ◦ JEP 384: Records (Second Preview) • Java 16 にて正式追加 ◦ JEP 395: Records Records - 概要 39
  30. Java 8 から 21 へのバージョンアップでどう変わる? • 手軽に不変なクラスが作成できる ◦ 記述量が少なく利用できる ▪

    コンストラクタや equals などの定義が不要 ▪ 独自の仕様・実装をオーバーライドも可能 ◦ フィールドは常に private final が付与 Records - 特徴・良いところ 40
  31. Java 8 から 21 へのバージョンアップでどう変わる? Records - イメージ 41 //

    内部的には以下と同様 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) { ... } public int hashCode() { ... } public String toString() { ... } } record Point(int x, int y) { }
  32. Java 8 から 21 へのバージョンアップでどう変わる? Records - イメージ 42 //

    内部的には以下と同様 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) { ... } public int hashCode() { ... } public String toString() { ... } } record Point(int x, int y) { } 少ない記述量で不変なクラスができる ※アクセサメソッドは "get" 始まりでない
  33. Java 8 から 21 へのバージョンアップでどう変わる? イミュータブルである性質を使って、DDD の文脈で • Value Object

    • DTO を定義するのに活かせる Records - Value Object、DTO 43 ※図表は、松岡幸一郎(@little_hand_s).ドメイン駆動設計 モデリング/実装ガ イド.p.79 図 7.1 より https://booth.pm/ja/items/1835632
  34. Java 8 から 21 へのバージョンアップでどう変わる? 44 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) { /* 最大公約数を求める */ } ... 続く
  35. Java 8 から 21 へのバージョンアップでどう変わる? 45 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) { /* 最大公約数を求める */ } ... 続く コンストラクタを上書き(入力値を通分して生成する) 分数のルール(=ドメイン知識)を保つことができる🎉
  36. Java 8 から 21 へのバージョンアップでどう変わる? 46 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) { /* 最大公約数を求める */ } ... 続く コンストラクタを上書き(入力値を通分して生成する) 分数のルール(=ドメイン知識)を保つことができる🎉 お分かり頂けただろうか
  37. Java 8 から 21 へのバージョンアップでどう変わる? 47 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) { /* 最大公約数を求める */ } ... 続く // 普通のコンストラクタ public Rational(int num, int denom) { int gcd = gcd(num, denom); num /= gcd; denom /= gcd; this.num = num; this.denom = denom; } 󰢏 コンストラクタも簡略化できる!   ※レコードのヘッダーと一致する場合に限る
  38. Java 8 から 21 へのバージョンアップでどう変わる? 48 Records - 例:分数(from JEP

    395) ... 続き 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 ); }
  39. Java 8 から 21 へのバージョンアップでどう変わる? 49 Records - 例:分数(from JEP

    395) ... 続き 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 ); } ドメインルールの流出を防ぐ 新しいインスタンスを生成・返却 分数のルールを呼び出し側に出さないように定義できる
  40. Java 8 から 21 へのバージョンアップでどう変わる? まとめ - 実装編 • Java

    は日々進化していく • 使える機能は積極的に取り入れる ◦ 良い開発者体験の向上につながる ◦ サービス価値の向上にもつながる(と思うよ) ◦ 旧来の書き方しか知らないのはもったいない • 日頃からの情報のアップデートは大事! 50
  41. Java 8 から 21 へのバージョンアップでどう変わる? バージョンアップでやったこと 以下の 3 点を実施 •

    バージョンアップに伴う影響の確認 • チーム内への展開 • プロダクトへの適用 52
  42. Java 8 から 21 へのバージョンアップでどう変わる? 非推奨要素の確認 確認時に参照したサイト • Oracle JDK

    Migration Guide ◦ 例: 17 までの Removed APIs ◦ 例: 8 → 17 での変更点 • Oracle Java SE/JDK API 仕様 - ◦ 例: 17 までの非推奨一覧 57
  43. Java 8 から 21 へのバージョンアップでどう変わる? 非推奨要素の確認 確認時に参照したサイト • Oracle JDK

    Migration Guide ◦ 例: 17 までの Removed APIs ◦ 例: 8 → 17 での変更点 • Oracle Java SE/JDK API 仕様 - ◦ 例: 17 までの非推奨一覧 58
  44. Java 8 から 21 へのバージョンアップでどう変わる? 非推奨要素の確認 確認時に参照したサイト • Oracle JDK

    Migration Guide ◦ 例: 17 までの Removed APIs ◦ 例: 8 → 17 での変更点 • Oracle Java SE/JDK API 仕様 - ◦ 例: 17 までの非推奨一覧 59
  45. Java 8 から 21 へのバージョンアップでどう変わる? 非推奨要素の確認 確認時に参照したサイト • Oracle JDK

    Migration Guide ◦ 例: 17 までの Removed APIs ◦ 例: 8 → 17 での変更点 • Oracle Java SE/JDK API 仕様 - ◦ 例: 17 までの非推奨一覧 60
  46. Java 8 から 21 へのバージョンアップでどう変わる? 先人たちの知恵を拝借 確認時に参照したサイト • きしださん(@kis)の Qiita

    記事 ◦ https://qiita.com/nowokay • さくらばさん(@skrb)の Java in the Box Annex ◦ https://www.javainthebox.com/ ▪ 「JEP では語られない」シリーズ 61 ここにあげられなかったものも含め、先人の皆さんありがとうございます!
  47. Java 8 から 21 へのバージョンアップでどう変わる? チーム内への展開 - 実施したこと・工夫 • 手順書作成

    ◦ 表現・言葉・手順の粒度を丁寧にした ▪ 役割・バックグラウンドが異なるメンバーが在籍 ▪ デザイナーにもわかるようにした • 部内説明 ◦ 常日頃から先の予定や現況をこまめにした ▪ 作業の進捗への影響を最小限にするため 63
  48. Java 8 から 21 へのバージョンアップでどう変わる? プロダクトへの適用 - 考えたこと • 開発プロセスとバージョンアップの整合性の確保について

    • 具体的には、主に以下の 3 つをどの流れでやるか ◦ 本番環境のランタイム ◦ 各開発者の開発環境 ◦ CI/CD パイプライン(ビルド)環境 ※ちなみに、テスト環境や検証環境は事前にテスト済み 65
  49. Java 8 から 21 へのバージョンアップでどう変わる? プロダクトへの適用 - うちの場合 以下の流れで実施 1.

    イテレーション成果物の確定 2. Ver.UP 資材を次イテレーションの成果物対象にマージ 3. チームへの手順の展開 4. 各開発者環境を Java 17 にして最終の確認を全体で行う 5. Ver.UP 資材がリリース対象に含まれる 6. 本番稼働環境全体で Java 17 として稼働する 67
  50. Java 8 から 21 へのバージョンアップでどう変わる? プロダクトへの適用 - うちの場合 69 この時点で、翌火曜日のリリース資材が確定

    このあとに • Java Ver.UP 資材を翌々火曜のリリースされるようにする • チームへの手順の展開
  51. Java 8 から 21 へのバージョンアップでどう変わる? プロダクトへの適用 - うちの場合 71 この時点で

    Ver.UP 資材が含まれているので、 CI/CD パイプライン上のビルドも 17 に上がる
  52. Java 8 から 21 へのバージョンアップでどう変わる? 苦労したこと バージョンアップ前後で苦労したことは以下の 2 つ •

    使用している端末そのものやその OS での差分があった • ログの出方が変わったため、運用の一部に影響があった 73
  53. Java 8 から 21 へのバージョンアップでどう変わる? 端末や OS の差分 - 内容

    以下の差分があった • OS の差分 ◦ 入社時に Windows と Mac を選択できる • 端末の仕様差分 ◦ Mac の CPU (Intel or M1/M2) など • Java (JDK) の指定方法が異なる ◦ 参画タイミングで環境構築の手順が異なっていたため 74
  54. Java 8 から 21 へのバージョンアップでどう変わる? 端末や OS の差分 - 対処方法

    • 事前 ◦ 手順書作成時に一部メンバーでリハーサル • 事後 ◦ 個別対応。。。 ▪ ケースバイケースで対応しました 75
  55. Java 8 から 21 へのバージョンアップでどう変わる? ログの仕様変更にともなう影響 - 対象 JEP 358:

    Helpful NullPointerExceptions • https://openjdk.org/jeps/358 • Java 14 から適用 • NPE 発生時のメッセージが細かく表現される 76
  56. Java 8 から 21 へのバージョンアップでどう変わる? ログの仕様変更にともなう影響 - 結果 エラー発生時のログ確認の運用に影響があった •

    メッセージが以前と違うことで一時的な混乱が発生 ◦ なにかあった・・!?みたいな感じ ◦ 原因が分かり次第、終息 77
  57. Java 8 から 21 へのバージョンアップでどう変わる? ログの仕様変更にともなう影響 - 原因 バージョンアップの影響確認時の考慮漏れ •

    実装やコード上で必要な変更に注視していた • 振る舞いについても影響確認が必要なのは学び 78
  58. Java 8 から 21 へのバージョンアップでどう変わる? まとめ - 運用 • バージョンアップを貯めると負担が大きい

    ◦ 基本は LTS のタイミングでいいはず ◦ チームでのバージョンアップ戦略は認識合わせが必要 • 影響確認においては様々な情報を照らし合わせる ◦ 実装上だけでなく、ログ、GCなどの振る舞いも含む • 日頃からの情報のアップデートは大事! ◦ あれ、実装編と同じやん つまり、日々学びですわー 79