虎の穴ラボ技術ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

クイズで学ぶ!JavaのSealed Class【Java 17に向けて】

こんにちは。虎の穴ラボの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での情報となります。

  1. Java: OpenJDK 17 Early-Access Buildsをダウンロードし、任意のフォルダに展開する
  2. IntelliJにて新規プロジェクトを作成する
  3. Project SDKのプルダウンからAdd JDK...を選択して、(1)で展開したフォルダを指定する
  4. Preferences...からBuild, Execution, Deployment>Compiler>Java Compilerと進む
  5. Additional command line parameters:に--enable-previewを設定する

Java 17にSealed Classが正式に取り込まれたら上記手順は不要になります。
Java 16を使ってSealed Classを試す場合は以下の手順を参考にしてください。

qiita.com

まずはちょっとした前提知識の問題からです。

問題

第0問

Sealed Classの日本語読みはなんでしょう?

答えと解説(クリックで開きます) 答え:シール・クラス
これはエラーを出してみるとわかります。
今回使用しているバージョンでは、まだSealed Classがプレビュー機能です。
そのため--enable-previewオプションをつけずに実行すると以下のメッセージが出力されます。

java: シール・クラスはプレビュー機能であり、デフォルトで無効になっています。
  (シール・クラスを有効にするには--enable-previewを使用します)

f:id:toranoana-lab:20210422191444p:plain
シール・クラス
日本人的にはついつい「シール"ド"」と言いたくなりますよね。
Sealedの意味についてですが、seal(密閉する)の過去分詞形になります。「盾(Shield)」ではありません。

第1問

次のパッケージ構成で、Fugaクラス(親クラス)の継承をHogeクラスのみ許したいとき、Fugaクラスはどう宣言すべきでしょうか?
f:id:toranoana-lab:20210423122028p:plain
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クラスを継承したクラスでは、finalsealednon-sealedのいづれかの修飾子が必要となります。
整理すると以下のようになります。

修飾子 意味
final どのクラスも継承不可
sealed 指定したクラスのみ継承可能
non-sealed どのクラスも継承可能

non-sealedはJavaでは珍しい(?)ハイフン付きの修飾子です。
なお、予約語ではありません。
参考 docs.oracle.com

キーワードとしては追加しますが、予約語にはしないという対応は最近多く見られ、できる限り後方互換性を維持するための判断となります。

第3問

次のパッケージ構成で、Fugaクラス(親クラス)の継承をHogeクラスのみ許したいとき、Fugaクラスはどう宣言すべきでしょうか?
f:id:toranoana-lab:20210423151819p:plain
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氏が解説している記事がありますので、そちらをご覧ください。

www.infoq.com

P.S.

直近のイベント情報
■4月28日(水)19:30~ テーマフリーのLT会を開催します。
 登壇者も募集中ですので、お気軽にお申し込みください!
yumenosora.connpass.com
採用情報
■募集職種
yumenosora.co.jp
カジュアル面談も随時開催中です
■お申し込みはこちら!
yumenosora.connpass.com

■ToraLab.fmスタートしました!
メンバーによるPodcastを配信中!
是非スキマ時間に聞いて頂けると嬉しいです。
anchor.fm
■Twitterもフォローしてくださいね!
ツイッターでも随時情報発信をしています
twitter.com