シールドクラス (Java 17+)
CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 52/87。
従来のJavaでは、クラスを作成すると、(finalとマークしない限り)他のどのクラスでもそれを継承できます。しかし、継承は許可したいが、誰からでもというわけではない場合はどうすればよいでしょうか?Java 17では、どのクラスが自分のクラスを継承できるかを正確に制御できるようにするために、封印クラス(sealed classes)が導入されました。
シールドクラスは、sealedおよびpermitsキーワードを使用して、許可されたサブクラスを明示的に宣言します:
sealed class Shape permits Circle, Rectangle, Triangle {
abstract double area();
}
final class Circle extends Shape {
double radius;
double area() { return Math.PI * radius * radius; }
}
final class Rectangle extends Shape {
double width, height;
double area() { return width * height; }
}
final class Triangle extends Shape {
double base, height;
double area() { return 0.5 * base * height; }
}Circle、Rectangle、および Triangle のみが Shape を拡張できます。それ以外のクラスが拡張しようとすると、コンパイルエラーが発生します。
許可された各サブクラスは、3つの修飾子のうちの1つを使用する必要があります:final(これ以上の拡張は不可)、sealed(制限チェーンを継続)、またはnon-sealed(無制限の拡張を許可):
sealed class Vehicle permits Car, Truck { }
final class Car extends Vehicle { } // 拡張不可
non-sealed class Truck extends Vehicle { } // 誰でもTruckを拡張可能シールドクラスは、コンパイラがすべての可能なサブタイプを把握し、網羅性を検証できるため、switch式でのパターンマッチングにおいて特に強力です。これらは、支払い方法、レスポンスタイプ、図形など、ドメイン内の固定された型のセットをモデリングする場合に最適です。
チャレンジ
簡単アプリケーション内に存在できる通知の種類を制限することで、シール(封印)クラスを実証する通知システムを構築しましょう。特定の通知タイプのみが許可される階層を作成し、各タイプが異なる方法で配信を処理するようにします。
コードを4つのファイルに分けて構成します:
Notification.java: すべての通知のベースとなるシール抽象クラスを作成します。これは、EmailNotification、SMSNotification、およびPushNotificationのちょうど3つのサブクラスを許可する必要があります。シールクラスには、protectedフィールドのmessage(String)、それを初期化するためのコンストラクタ、メッセージのゲッター、および通知の送信方法を説明するStringを返す抽象メソッドdeliver()が必要です。EmailNotification.java: Notificationを継承するfinalクラスを作成します。EmailNotificationには、メールアドレス用の追加のprivateフィールドrecipient(String) があります。そのコンストラクタはメッセージと受信者の両方を受け取り、super()を使用して親コンストラクタを呼び出す必要があります。deliver()メソッドは、"Sending email to " + recipient + ": " + messageを返す必要があります。SMSNotification.java: Notificationを継承するnon-sealedクラスを作成します。これにより、将来必要に応じて他のクラスがこれを継承できるようになります。SMSNotificationには、追加のprivateフィールドphoneNumber(String) があります。そのコンストラクタはメッセージと電話番号を受け取ります。deliver()メソッドは、"Sending SMS to " + phoneNumber + ": " + messageを返す必要があります。PushNotification.java: Notificationを継承するfinalクラスを作成します。PushNotificationには、追加のprivateフィールドdeviceId(String) があります。そのコンストラクタはメッセージとデバイスIDを受け取ります。deliver()メソッドは、"Sending push to device " + deviceId + ": " + messageを返す必要があります。Main.java: 通知システムを完成させましょう!メッセージ (String)、メールアドレス (String)、電話番号 (String)、およびデバイスID (String) の4つの入力を受け取ります。同じメッセージを使用し、それぞれの連絡先情報を使用して、各通知タイプのインスタンスを1つずつ作成します。シールクラスによるポリモーフィズムを実証するために、3つすべてを
Notification[]型の配列に格納します。次に、配列を反復処理し、各通知でdeliver()を呼び出した結果を出力します。
メッセージ、メールアドレス、電話番号、デバイスIDの順に4つの入力を受け取ります。
シールクラスがどのように階層を制限しているかに注目してください。許可された3つのクラスのみが Notification を継承できます。各サブクラスは、自身を final、sealed、または non-sealed として宣言する必要があります。これにより、ポリモーフィズムを有効にしながら、型階層を完全に制御できるようになります!
チートシート
シールドクラス(sealed class)は、sealedおよびpermitsキーワードを使用して、どのクラスがそれを拡張できるかを制限します:
sealed class Shape permits Circle, Rectangle, Triangle {
abstract double area();
}permitsにリストされているクラスのみが、そのシールドクラスを拡張できます。それ以外のクラスが拡張しようとすると、コンパイルエラーが発生します。
許可された各サブクラスは、次の3つの修飾子のいずれかを使用する必要があります:
final- さらなる拡張を禁止しますsealed- 独自の許可されたサブクラスを使用して制限チェーンを継続しますnon-sealed- 制限のない拡張のために階層を公開します
sealed class Vehicle permits Car, Truck { }
final class Car extends Vehicle { } // 拡張できません
non-sealed class Truck extends Vehicle { } // 誰でもTruckを拡張できますシールドクラスは、支払い方法、レスポンスタイプ、幾何学図形など、ドメイン内の固定された型のセットをモデリングするのに役立ちます。コンパイラがすべての可能なサブタイプを把握しているため、switch式のパターンマッチングとうまく連携します。
自分で試してみよう
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 4つの入力を読み込む
String message = scanner.nextLine();
String email = scanner.nextLine();
String phoneNumber = scanner.nextLine();
String deviceId = scanner.nextLine();
// TODO: 同じ message を使用して、各通知タイプのインスタンスを1つずつ作成する
// - message と email を使用した EmailNotification
// - message と phoneNumber を使用した SMSNotification
// - message と deviceId を使用した PushNotification
// TODO: 3つすべての通知を Notification[] 配列に格納する
// これは sealed クラスを用いたポリモーフィズムのデモンストレーションです
// TODO: 配列をループで回し、各通知の deliver() の結果を出力する
// 各通知に対して
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4継承
継承の基本 (extends)super キーワードメソッドのオーバーライド (@Override)コンストラクタチェーンObject クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造