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

シールドクラスをはじめよう / Getting Started with Sealed Cla...

mackey0225
October 27, 2024

シールドクラスをはじめよう / Getting Started with Sealed Classes

2024-10-27 に開催された JJUG CCC 2024 Fall での登壇資料です。
https://jjug.doorkeeper.jp/events/177443

mackey0225

October 27, 2024
Tweet

More Decks by mackey0225

Other Decks in Programming

Transcript

  1. 1: Java 7 以前 2: Java 8 3: Java 11

    4: Java 17 5: Java 21 6: Java 22 以降
  2. 1: Java 7 以前 2: Java 8 3: Java 11

    4: Java 17 5: Java 21 6: Java 22 以降 今日の話は Java 17 の追加機能です!
  3. 1: Java 7 以前 2: Java 8 3: Java 11

    4: Java 17 5: Java 21 6: Java 22 以降 この話で Ver.UP の モチベ上げられたら嬉しい
  4. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 自己紹介 名前:浅野 正貴 所属:BABY JOB 株式会社 最近はインフラや SRE

    がメイン X: @mackey0225 タイトルは最近の O'Reilly の書籍より拝借 JJUG CCC 2024 Fall ロゴスポンサーです!!
  5. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 諸注意 • 本セッションは Step UP なので基礎重視 •

    アクセス修飾子や static 修飾子は割愛 • コンストラクタやアクセッサも割愛 • 呼び方は「シール・クラス」が一般的
  6. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 諸注意 • 本セッションは Step UP なので基礎重視 •

    アクセス修飾子や static 修飾子は割愛 • コンストラクタやアクセッサも割愛 • 呼び方は「シール・クラス」が一般的
  7. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i どんな機能なの? • sealed という名の通り、蓋をする ◦ 継承・実装の関係を制限 •

    宣言的な定義により、以下を実現 ◦ コンパイルフェーズでの整合性確保→保守性の向上 • リフレクション(黒魔術)も追加(今日は割愛) • Project Amber のいち機能 ◦ Java 15 でプレビュー、Java 17 で正式追加
  8. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i もう少し踏み込んで:Project Amber について • 生産性向上を目的とした機能の提供 • 他の機能例

    ◦ ローカル変数の var の導入 ◦ レコードクラス ◦ switch のパターンマッチ ◦ パブリックスタティックヴォイドメイン からの解放(現在 Preview)
  9. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 基本的な使い方(イメージ) sealed class 親 permits 長子, 中間子,

    末子 {} sealed class 長子 extends 親 permits 孫 {} final class 中間子 extends 親 {} non-sealed class 末子 extends 親 {} final class 孫 extends 長子 {}
  10. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i sealed 修飾子 / permits • Java 17

    で正式追加 • 継承・実装する対象を制限する際に宣言 • permits 句内で許可対象を指定する • 継承・実装側はさらに制限の指定が必要 ◦ sealed, non-sealed, final のいずれかを指定が必要
  11. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i permits 句にない対象は継承・実装できない sealed class 親 permits 子

    {} final class 子 extends 親 {} // こっちは OK final class 他人 extends 親 {} // こっちは赤の他人なので NG
  12. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i permits 句にない対象は継承・実装できない sealed class 親 permits 子

    {} final class 子 extends 親 {} // こっちは OK final class 他人 extends 親 {} // こっちは赤の他人なので NG
  13. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i non-sealed 修飾子 • sealed と同じく Java 17

    から正式追加 • sealed とは逆に継承や実装に制限をかけない ◦ 以降は自由に継承・実装していいよ • 元側で sealed がないのに宣言するとコンパイルエラー ◦ 要は「何、勝手なこと言っているんだ?」状態
  14. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i final 修飾子 • 当初から存在 • final がついたクラスを継承することはできない

    • インターフェースには使えない(コンパイルエラー) ◦ 実装前提だから、それはそう • abstract とは併用できない(コンパイルエラー) ◦ 継承前提だから、それはそう
  15. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i スーパー側が sealed 設定時の挙動 sealed class Foo permits

    Bar {} class Bar extends Foo {} // 継承元の sealed 修飾子を無視できない
  16. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i スーパー側が sealed 設定時の挙動 sealed class Foo permits

    Bar {} class Bar extends Foo {} // 継承元の sealed 修飾子を無視できない
  17. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i switch のパターンマッチ • Java 21 から以下が追加されている ◦

    JEP 441: Pattern Matching for switch ◦ https://openjdk.org/jeps/441 • case ラベルに型を記述して条件分岐できる ◦ 具体的な仕様はこのあと説明
  18. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i パターンマッチにおける網羅性【sealed 不使用】 interface 決済 {} class 現金決済

    implements 決済 {} class クレカ決済 implements 決済 {} switch (k) { case 現金決済 g -> System.out.println("現金"); case クレカ決済 c -> System.out.println("クレカ"); }
  19. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i パターンマッチにおける網羅性【sealed 不使用】 interface 決済 {} class 現金決済

    implements 決済 {} class クレカ決済 implements 決済 {} switch (k) { case 現金決済 g -> System.out.println("現金"); case クレカ決済 c -> System.out.println("クレカ"); } このままでもいけそうな気がするけど、 default が必要 → コンパイルエラー
  20. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i パターンマッチにおける網羅性【sealed 不使用】 interface 決済 {} class 現金決済

    implements 決済 {} class クレカ決済 implements 決済 {} switch (k) { case 現金決済 g -> System.out.println("現金"); case クレカ決済 c -> System.out.println("クレカ"); default -> System.out.println("デフォルト "); }
  21. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i パターンマッチにおける網羅性【sealed 不使用】 interface 決済 {} class 現金決済

    implements 決済 {} class クレカ決済 implements 決済 {} switch (k) { case 現金決済 g -> System.out.println("現金"); case クレカ決済 c -> System.out.println("クレカ"); default -> System.out.println("デフォルト "); } では、sealed を 使うとどうなるか?
  22. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i パターンマッチにおける網羅性【sealed 使用】 sealed interface 決済 permits 現金決済,

    クレカ決済 {} final class 現金決済 implements 決済 {} final class クレカ決済 implements 決済 {} switch (k) { case 現金決済 g -> System.out.println("現金"); case クレカ決済 c -> System.out.println("クレカ"); }
  23. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i パターンマッチにおける網羅性【sealed 使用】 sealed interface 決済 permits 現金決済,

    クレカ決済 {} final class 現金決済 implements 決済 {} final class クレカ決済 implements 決済 {} switch (k) { case 現金決済 g -> System.out.println("現金"); case クレカ決済 c -> System.out.println("クレカ"); } 実装するクラスが明示して列挙されているので default が不要になった!\(^o^)/
  24. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i パターンマッチにおける網羅性【sealed 使用】 sealed interface 決済 permits 現金決済,

    クレカ決済 {} final class 現金決済 implements 決済 {} final class クレカ決済 implements 決済 {} switch (k) { case 現金決済 g -> System.out.println("現金"); case クレカ決済 c -> System.out.println("クレカ"); } 実装するクラスが明示して列挙されているので default が不要になった!\(^o^)/ え、メリットって それだけ?
  25. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i パターンマッチにおける網羅性【sealed 使用】 sealed interface 決済 permits 現金決済,

    クレカ決済 {} final class 現金決済 implements 決済 {} final class クレカ決済 implements 決済 {} switch (k) { case 現金決済 g -> System.out.println("現金"); case クレカ決済 c -> System.out.println("クレカ"); } 実装するクラスが明示して列挙されているので default が不要になった!\(^o^)/ いやいや それだけじゃないんよ
  26. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 新たに実装が増えたとき sealed interface 決済 permits 現金決済, クレカ決済

    {} final class 現金決済 implements 決済 {} final class クレカ決済 implements 決済 {} switch (k) { case 現金決済 g -> System.out.println("現金"); case クレカ決済 c -> System.out.println("クレカ"); }
  27. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 新たに実装が増えたとき sealed interface 決済 permits 現金決済, クレカ決済

    , QR決済 {} final class 現金決済 implements 決済 {} final class クレカ決済 implements 決済 {} final class QR決済 implements 決済 {} switch (k) { case 現金決済 g -> System.out.println("現金"); case クレカ決済 c -> System.out.println("クレカ"); }
  28. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 新たに実装が増えたとき sealed interface 決済 permits 現金決済, クレカ決済

    , QR決済 {} final class 現金決済 implements 決済 {} final class クレカ決済 implements 決済 {} final class QR決済 implements 決済 {} switch (k) { case 現金決済 g -> System.out.println("現金"); case クレカ決済 c -> System.out.println("クレカ"); } コンパイルフェーズで QR決済ラベルが足りない指摘をしてくれる
  29. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 新たに実装が増えたとき sealed interface 決済 permits 現金決済, クレカ決済

    , QR決済 {} final class 現金決済 implements 決済 {} final class クレカ決済 implements 決済 {} final class QR決済 implements 決済 {} switch (k) { case 現金決済 g -> System.out.println("現金"); case クレカ決済 c -> System.out.println("クレカ"); } コンパイルフェーズで QR決済ラベルが足りない指摘をしてくれる 実装漏れや考慮漏れを防げる
  30. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 代数的データ型 代数的データ型(Algebraic Data Types)とは... • 集合の 直和

    と 直積 で型を表現する ◦ 直和:enum クラス(どれか一つをとる) ◦ 直積:record クラス(カラムの掛け合わ せ)
  31. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 代数的データ型 代数的データ型(Algebraic Data Types)とは... • 集合の 直和

    と 直積 で型を表現する ◦ 直和:enum クラス(どれか一つをとる) ◦ 直積:record クラス(カラムの掛け合わ せ) とりあえず、雰囲気だけでOK!
  32. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i • 契約ID 例:契約状態の表現 未契約 • 契約ID •

    開始日 • 契約ID • 開始日 • 終了日 契約中 解約 状態で必要な項目(開始日と終了日)の有無が異なる
  33. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 例:契約状態の表現(代数的データ型でない) enum 契約状態 {未契約,契約中,解約;} class 契約 {

    int id; LocalDate 開始日; LocalDate 終了日; 契約状態 契約状態; } // 未契約の生成 new 契約(1, null, null, 契約状態.未契約); // 契約中の生成 new 契約(2, 開始日, null, 契約状態.契約中); // 解約の生成 new 契約(3, 開始日, 終了日, 契約状態.解約);
  34. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 例:契約状態の表現(代数的データ型でない) enum 契約状態 {未契約,契約中,解約;} class 契約 {

    int id; LocalDate 開始日; LocalDate 終了日; 契約状態 契約状態; } // 未契約の生成 new 契約(1, null, null, 契約状態.未契約); // 契約中の生成 new 契約(2, 開始日, null, 契約状態.契約中); // 解約の生成 new 契約(3, 開始日, 終了日, 契約状態.解約); 状態に応じて、不要な日付項目に null いれる
  35. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 例:契約状態の表現(代数的データ型でない) enum 契約状態 {未契約,契約中,解約;} class 契約 {

    int id; LocalDate 開始日; LocalDate 終了日; 契約状態 契約状態; } // 未契約の生成 new 契約(1, null, null, 契約状態.未契約); // 契約中の生成 new 契約(2, 開始日, null, 契約状態.契約中); // 解約の生成 new 契約(3, 開始日, 終了日, 契約状態.解約); 状態に応じて、不要な日付項目に null いれる 整合性をロジック・コードで担保する 💪
  36. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 例:契約状態の表現(代数的データ型でない) enum 契約状態 {未契約,契約中,解約;} class 契約 {

    int id; LocalDate 開始日; LocalDate 終了日; 契約状態 契約状態; } // 未契約の生成 new 契約(1, null, null, 契約状態.未契約); // 契約中の生成 new 契約(2, 開始日, null, 契約状態.契約中); // 解約の生成 new 契約(3, 開始日, 終了日, 契約状態.解約); 状態に応じて、不要な日付項目に null いれる 整合性をロジック・コードで担保する 💪 バリデーションや複雑なファクトリが増える... ☹
  37. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 例:契約状態の表現(代数的データ型でない) enum 契約状態 {未契約,契約中,解約;} class 契約 {

    int id; LocalDate 開始日; LocalDate 終了日; 契約状態 契約状態; } // 未契約の生成 new 契約(1, null, null, 契約状態.未契約); // 契約中の生成 new 契約(2, 開始日, null, 契約状態.契約中); // 解約の生成 new 契約(3, 開始日, 終了日, 契約状態.解約); 状態に応じて、不要な日付項目に null いれる 整合性をロジック・コードで担保する 💪 バリデーションや複雑なファクトリが増える... ☹ そこに時間や労力を 掛けるのはもったいないかも
  38. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 例:契約状態の表現(代数的データ型) sealed interface 契約 permits 未契約, 契約中,

    解約 { int id(); } record 未契約(int id) implements 契約 {} record 契約中(int id, LocalDate 開始日) implements 契約 {} record 解約(int id, LocalDate 開始日, LocalDate 終了日) implements 契約 {}
  39. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 例:契約状態の表現(代数的データ型) sealed interface 契約 permits 未契約, 契約中,

    解約 { int id(); } record 未契約(int id) implements 契約 {} record 契約中(int id, LocalDate 開始日) implements 契約 {} record 解約(int id, LocalDate 開始日, LocalDate 終了日) implements 契約 {} // 未契約の生成 new 未契約(1); // 契約中の生成 new 契約中(2, 開始日); // 解約の生成 new 解約(3, 開始日, 終了日);
  40. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 例:契約状態の表現(代数的データ型) sealed interface 契約 permits 未契約, 契約中,

    解約 { int id(); } record 未契約(int id) implements 契約 {} record 契約中(int id, LocalDate 開始日) implements 契約 {} record 解約(int id, LocalDate 開始日, LocalDate 終了日) implements 契約 {} // 未契約の生成 new 未契約(1); // 契約中の生成 new 契約中(2, 開始日); // 解約の生成 new 解約(3, 開始日, 終了日); 不要な項目は設定できない
  41. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 例:契約状態の表現(代数的データ型) sealed interface 契約 permits 未契約, 契約中,

    解約 { int id(); } record 未契約(int id) implements 契約 {} record 契約中(int id, LocalDate 開始日) implements 契約 {} record 解約(int id, LocalDate 開始日, LocalDate 終了日) implements 契約 {} // 未契約の生成 new 未契約(1); // 契約中の生成 new 契約中(2, 開始日); // 解約の生成 new 解約(3, 開始日, 終了日); 不要な項目は設定できない 型で整合性を担保し、型で表現する
  42. シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 例:契約状態の表現(代数的データ型) sealed interface 契約 permits 未契約, 契約中,

    解約 { int id(); } record 未契約(int id) implements 契約 {} record 契約中(int id, LocalDate 開始日) implements 契約 {} record 解約(int id, LocalDate 開始日, LocalDate 終了日) implements 契約 {} // 未契約の生成 new 未契約(1); // 契約中の生成 new 契約中(2, 開始日); // 解約の生成 new 解約(3, 開始日, 終了日); 不要な項目は設定できない 型で整合性を担保し、型で表現する コンパイル時に気づくことができる さらに先のパターンマッチとの併用でより効果的に扱える