Slide 1

Slide 1 text

シールドクラスを はじめよう 2024-10-27 JJUG CCC 2024 Fall BABY JOB 株式会社 浅野 正貴 (@mackey0225)

Slide 2

Slide 2 text

さて、みなさん!

Slide 3

Slide 3 text

Java のバージョンは 何をつかっていますか?

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

1: Java 7 以前 2: Java 8 3: Java 11 4: Java 17 5: Java 21 6: Java 22 以降 この話で Ver.UP の モチベ上げられたら嬉しい

Slide 7

Slide 7 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 自己紹介 名前:浅野 正貴 所属:BABY JOB 株式会社 最近はインフラや SRE がメイン X: @mackey0225 タイトルは最近の O'Reilly の書籍より拝借

Slide 8

Slide 8 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 自己紹介 名前:浅野 正貴 所属:BABY JOB 株式会社 最近はインフラや SRE がメイン X: @mackey0225 タイトルは最近の O'Reilly の書籍より拝借 JJUG CCC 2024 Fall ロゴスポンサーです!!

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i アジェンダ ● sealed の概要 ● 仕様と簡単な例 ● 応用 ● まとめ

Slide 12

Slide 12 text

sealed の概要

Slide 13

Slide 13 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i どんな機能なの? ● sealed という名の通り、蓋をする ○ 継承・実装の関係を制限 ● 宣言的な定義により、以下を実現 ○ コンパイルフェーズでの整合性確保→保守性の向上 ● リフレクション(黒魔術)も追加(今日は割愛) ● Project Amber のいち機能 ○ Java 15 でプレビュー、Java 17 で正式追加

Slide 14

Slide 14 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i もう少し踏み込んで:Project Amber について ● 生産性向上を目的とした機能の提供 ● 他の機能例 ○ ローカル変数の var の導入 ○ レコードクラス ○ switch のパターンマッチ ○ パブリックスタティックヴォイドメイン からの解放(現在 Preview)

Slide 15

Slide 15 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i もう少し踏み込んで:Project Amber について 詳細は下記 https://openjdk.org/projects/amber/

Slide 16

Slide 16 text

仕様と簡単な例

Slide 17

Slide 17 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i シールドクラスにおける登場人物 ● sealed 修飾子 / permits 句 ● non-sealed 修飾子 ● final 修飾子 ● 修飾子なし

Slide 18

Slide 18 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 基本的な使い方(イメージ) sealed class 親 permits 長子, 中間子, 末子 {} sealed class 長子 extends 親 permits 孫 {} final class 中間子 extends 親 {} non-sealed class 末子 extends 親 {} final class 孫 extends 長子 {}

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i non-sealed 修飾子 ● sealed と同じく Java 17 から正式追加 ● sealed とは逆に継承や実装に制限をかけない ○ 以降は自由に継承・実装していいよ ● 元側で sealed がないのに宣言するとコンパイルエラー ○ 要は「何、勝手なこと言っているんだ?」状態

Slide 23

Slide 23 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i non-sealed 単独ではコンパイルエラー class Foo {} non-sealed class Bar extends Foo {} // 急な non-sealed はダメ

Slide 24

Slide 24 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i non-sealed 単独ではコンパイルエラー class Foo {} non-sealed class Bar extends Foo {} // 急な non-sealed はダメ

Slide 25

Slide 25 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i final 修飾子 ● 当初から存在 ● final がついたクラスを継承することはできない ● インターフェースには使えない(コンパイルエラー) ○ 実装前提だから、それはそう ● abstract とは併用できない(コンパイルエラー) ○ 継承前提だから、それはそう

Slide 26

Slide 26 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 修飾子なし ● 当初から存在 ○ というか、無いのがある意味普通 ● シールドクラスを継承時、修飾子なしはコンパイルエラー ○ インターフェースでも同様 ○ ただし、暗黙的に final のものは問題なし ■ 例: record や enum など

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i スーパー側が sealed 設定時の挙動 sealed class Foo permits Bar {} non-sealed class Bar extends Foo {} class Baz extends Bar {} // これは OK

Slide 30

Slide 30 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i スーパー側が sealed 設定時の挙動 sealed class Foo permits Bar {} non-sealed class Bar extends Foo {} class Baz extends Bar {} // これは OK

Slide 31

Slide 31 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i スーパー側が sealed 設定時の挙動 sealed interface Foo permits Bar {} record Bar(int id) implements Foo {} // これは OK (暗黙的 final)

Slide 32

Slide 32 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i スーパー側が sealed 設定時の挙動 sealed interface Foo permits Bar {} record Bar(int id) implements Foo {} // これは OK (暗黙的 final)

Slide 33

Slide 33 text

応用例

Slide 34

Slide 34 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 応用例のお品書き ● パターンマッチにおける網羅性 ● 代数的データ型(Algebraic Data Types)

Slide 35

Slide 35 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i switch のパターンマッチ ● Java 21 から以下が追加されている ○ JEP 441: Pattern Matching for switch ○ https://openjdk.org/jeps/441 ● case ラベルに型を記述して条件分岐できる ○ 具体的な仕様はこのあと説明

Slide 36

Slide 36 text

https://openjdk.org/jeps/441 より

Slide 37

Slide 37 text

https://openjdk.org/jeps/441 より if else と instanceof を 組み合わせて実装

Slide 38

Slide 38 text

https://openjdk.org/jeps/441 より

Slide 39

Slide 39 text

case ラベルに型を書くことで 条件分岐ができる https://openjdk.org/jeps/441 より

Slide 40

Slide 40 text

https://openjdk.org/jeps/441 より 加えて、switch 「式」なので、 再代入せず、直接 return している

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

シールドクラスをはじめよう #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("デフォルト "); }

Slide 44

Slide 44 text

シールドクラスをはじめよう #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 を 使うとどうなるか?

Slide 45

Slide 45 text

シールドクラスをはじめよう #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("クレカ"); }

Slide 46

Slide 46 text

シールドクラスをはじめよう #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^)/

Slide 47

Slide 47 text

シールドクラスをはじめよう #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^)/ え、メリットって それだけ?

Slide 48

Slide 48 text

シールドクラスをはじめよう #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^)/ いやいや それだけじゃないんよ

Slide 49

Slide 49 text

シールドクラスをはじめよう #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("クレカ"); }

Slide 50

Slide 50 text

シールドクラスをはじめよう #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("クレカ"); }

Slide 51

Slide 51 text

シールドクラスをはじめよう #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決済ラベルが足りない指摘をしてくれる

Slide 52

Slide 52 text

シールドクラスをはじめよう #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決済ラベルが足りない指摘をしてくれる 実装漏れや考慮漏れを防げる

Slide 53

Slide 53 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 応用例のお品書き ● パターンマッチにおける網羅性 ● 代数的データ型(Algebraic Data Types)

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 代数的データ型 代数的データ型(Algebraic Data Types)とは... ● 集合の 直和 と 直積 で型を表現する ○ 直和:enum クラス(どれか一つをとる) ○ 直積:record クラス(カラムの掛け合わ せ) とりあえず、雰囲気だけでOK!

Slide 56

Slide 56 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i ● 契約ID 例:契約状態の表現 未契約 ● 契約ID ● 開始日 ● 契約ID ● 開始日 ● 終了日 契約中 解約

Slide 57

Slide 57 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i ● 契約ID 例:契約状態の表現 未契約 ● 契約ID ● 開始日 ● 契約ID ● 開始日 ● 終了日 契約中 解約 状態で必要な項目(開始日と終了日)の有無が異なる

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

シールドクラスをはじめよう #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 契約 {}

Slide 65

Slide 65 text

シールドクラスをはじめよう #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, 開始日, 終了日);

Slide 66

Slide 66 text

シールドクラスをはじめよう #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, 開始日, 終了日); 不要な項目は設定できない

Slide 67

Slide 67 text

シールドクラスをはじめよう #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, 開始日, 終了日); 不要な項目は設定できない 型で整合性を担保し、型で表現する

Slide 68

Slide 68 text

シールドクラスをはじめよう #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, 開始日, 終了日); 不要な項目は設定できない 型で整合性を担保し、型で表現する コンパイル時に気づくことができる さらに先のパターンマッチとの併用でより効果的に扱える

Slide 69

Slide 69 text

まとめ

Slide 70

Slide 70 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i まとめ ● コンパイルフェーズで防げるのは魅力 ● その他の機能と組み合わせることで型の表現力が上がる ● とはいえ、チームの状況に合わせてね ○ 用法用量を守ることが大事 ● 大前提が Java 17 ○ 定期的なバージョンアップも必要 ○ Java はドンドン進化する

Slide 71

Slide 71 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 参考 [Project Amber について] https://openjdk.org/projects/amber/ https://openjdk.org/projects/amber/design-notes/rec ords-and-sealed-classes

Slide 72

Slide 72 text

シールドクラスをはじめよう #jjug_ccc #jjug_ccc_i 参考 [Sealed Classes] https://openjdk.org/jeps/409 [Pattern Matching for switch] https://openjdk.org/jeps/441

Slide 73

Slide 73 text

ご清聴いただき ありがとうございました