こんにちは。虎の穴ラボのH.Kです。
さて、3/16にJava 16がリリースされ、LTSであるJava 17のリリースまで半年を切りました。
そこで今回はJava 17の予習ということでSealed Classをクイズ形式で紹介します。
そもそもSealed Classとは
継承できるクラスを制限する機能です。
やたら継承されると困る!というクラスをsealed class Hoge
と宣言することで、継承可能なクラスを限定できます。
Java 15で1st Preview
、Java 16で2nd Preview
ということで、次回のJava 17では正式に入るのではないかと言われています。
環境情報
Java: OpenJDK 17 Early-Access Builds(Build 18)
※Java 17リリース時には仕様が変わっている可能性があります。
動作確認環境
- OS:macOS
- プロセッサ:Intel
参考情報:IntelliJでの試し方
執筆時点、Macでの情報となります。
- Java: OpenJDK 17 Early-Access Buildsをダウンロードし、任意のフォルダに展開する
- IntelliJにて新規プロジェクトを作成する
- Project SDKのプルダウンから
Add JDK...
を選択して、(1)で展開したフォルダを指定する Preferences...
からBuild, Execution, Deployment
>Compiler
>Java Compiler
と進む- Additional command line parameters:に
--enable-preview
を設定する
Java 17にSealed Classが正式に取り込まれたら上記手順は不要になります。
Java 16を使ってSealed Classを試す場合は以下の手順を参考にしてください。
まずはちょっとした前提知識の問題からです。
問題
第0問
Sealed Classの日本語読みはなんでしょう?
答えと解説(クリックで開きます)
答え:シール・クラス
これはエラーを出してみるとわかります。
今回使用しているバージョンでは、まだSealed Classがプレビュー機能です。
そのため--enable-preview
オプションをつけずに実行すると以下のメッセージが出力されます。
java: シール・クラスはプレビュー機能であり、デフォルトで無効になっています。 (シール・クラスを有効にするには--enable-previewを使用します)
日本人的にはついつい「シール"ド"」と言いたくなりますよね。
Sealedの意味についてですが、seal(密閉する)の過去分詞形になります。「盾(Shield)」ではありません。
第1問
次のパッケージ構成で、Fuga
クラス(親クラス)の継承をHoge
クラスのみ許したいとき、Fuga
クラスはどう宣言すべきでしょうか?
Hoge.java
package question1; final public class Hoge extends Fuga {}
答えと解説(クリックで開きます)
答え
Fuga.java
package question1; sealed public class Fuga permits Hoge {}
同じパッケージ内のクラスのみに継承を許す場合はpermits
を使って対象を指定します。
なお、アクセス修飾子はpublicでなくても問題ありません。
複数のクラスを継承先に指定したい場合は
sealed public class Fuga permits Hoge, Piyo {}
のようにカンマ(,)区切りで対象を記載します。
また、permits
に指定されているクラス(上記例ではHogeクラス)が、Sealedクラス(上記例ではFugaクラス)を継承していない場合、コンパイルエラーになります。
第2問
Sealedクラスを継承したHogeクラスがあります。
Hogeクラスはどのクラスも継承可能にしようとするとどのように宣言すればよいでしょうか?
Fuga.java
sealed public class Fuga permits Fuga.Hoge { public class Hoge extends Fuga {} }
答えと解説(クリックで開きます)
答え:non-sealed
で宣言する
sealed public class Fuga permits Fuga.Hoge { non-sealed public class Hoge extends Fuga {} }
non-sealed
で宣言することにより、制限がなくなり、一般的なクラスと同じ状態になります。
Sealedクラスを継承したクラスでは、final
、sealed
、non-sealed
のいづれかの修飾子が必要となります。
整理すると以下のようになります。
修飾子 | 意味 |
---|---|
final | どのクラスも継承不可 |
sealed | 指定したクラスのみ継承可能 |
non-sealed | どのクラスも継承可能 |
non-sealed
はJavaでは珍しい(?)ハイフン付きの修飾子です。
なお、予約語ではありません。
参考
docs.oracle.com
キーワードとしては追加しますが、予約語にはしないという対応は最近多く見られ、できる限り後方互換性を維持するための判断となります。
第3問
次のパッケージ構成で、Fuga
クラス(親クラス)の継承をHoge
クラスのみ許したいとき、Fuga
クラスはどう宣言すべきでしょうか?
Hoge.java
package question3.piyo; import question3.Fuga; final public class Hoge extends Fuga {}
答えと解説(クリックで開きます)
答え:できません。
ひっかけみたいな問題になってしまって申し訳ありません。
Sealedクラスを使い継承先を制限する場合、親クラスと子クラスは同じパッケージにある必要があります。
使い所が難しいですね。
まとめ
クイズ形式で見ていきましたが、これだけだとメリットがわからない方も多いかと思います。
将来的にはパターンマッチ機能を備えたswitch文/式と組み合わせて、 default値なしで以下のように書けるようになるようです。
sealed interface じゃんけん permits グー, チョキ, パー {} final class グー implements じゃんけん {} final class チョキ implements じゃんけん {} final class パー implements じゃんけん {} int 指の本数(じゃんけん j) { switch (j) { case グー g -> return 0; case チョキ c -> return 2; case パー p -> return 5; } }
これは継承先が制限されることにより、コンパイル時に網羅性が担保できるためです。
未来の便利機能への下準備ということで、詳しくは言語設計者であるBrian Goetz氏が解説している記事がありますので、そちらをご覧ください。
P.S.
直近のイベント情報
■4月28日(水)19:30~ テーマフリーのLT会を開催します。
登壇者も募集中ですので、お気軽にお申し込みください!
yumenosora.connpass.com
採用情報
■募集職種
yumenosora.co.jp
カジュアル面談も随時開催中です
■お申し込みはこちら!
yumenosora.connpass.com
■ToraLab.fmスタートしました!
メンバーによるPodcastを配信中!
是非スキマ時間に聞いて頂けると嬉しいです。
anchor.fm
■Twitterもフォローしてくださいね!
ツイッターでも随時情報発信をしています
twitter.com