シールドクラス (Java 17+)
CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 52/87。
従来のJavaでは、クラスを作成すると、(finalとマークしない限り)他のどのクラスでもそのクラスを継承できます。しかし、継承は許可したいものの、誰からでもというわけではない場合はどうすればよいでしょうか?Java 17では、どのクラスが自分のクラスを継承できるかを正確に制御できるようにするために、シールドクラス(sealed classes)が導入されました。
シールドクラスは、sealedおよびpermitsキーワードを使用して、許可されたサブクラスを明示的に宣言します:
sealed abstract 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 を継承できます。それ以外のクラスが継承しようとすると、コンパイルエラーが発生します。
許可された各サブクラスは、final(それ以上の拡張不可)、sealed(制限チェーンを継続)、または non-sealed(無制限の拡張を許可)の3つの修飾子のうちの1つを使用する必要があります:
sealed class Vehicle permits Car, Truck { }
final class Car extends Vehicle { } // 拡張不可
non-sealed class Truck extends Vehicle { } // 誰でもTruckを拡張可能シールドクラスは、コンパイラがすべての可能なサブタイプを把握し、網羅性を検証できるため、switch式でのパターンマッチングにおいて特に強力です。これらは、支払い方法、レスポンスタイプ、図形など、ドメイン内の固定された型のセットをモデリングする場合に最適です。
チャレンジ
簡単アプリケーション内に存在できる通知の種類を制限することで、sealedクラス(封印されたクラス)を実証する通知システムを構築しましょう。特定の通知タイプのみが許可され、各タイプが異なる方法で配信を処理する階層を作成します。
コードを4つのファイルに分けて構成します:
Notification.java: すべての通知のベースとなるsealed抽象クラスを作成します。これは、EmailNotification、SMSNotification、およびPushNotificationのちょうど3つのサブクラスを許可(permit)する必要があります。このsealedクラスには、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つずつ作成します。sealedクラスによるポリモーフィズム(多態性)を示すために、3つすべてを
Notification[]型の配列に格納します。次に、配列を反復処理し、各通知でdeliver()を呼び出した結果を出力します。
入力は、メッセージ、メールアドレス、電話番号、デバイスIDの順に4つ与えられます。
sealedクラスがどのように階層を制限しているかに注目してください。許可された3つのクラスのみが Notification を継承できます。各サブクラスは、自身を final、sealed、または non-sealed として宣言する必要があります。これにより、ポリモーフィズムを有効にしながら、型階層を完全に制御できるようになります!
自分で試してみよう
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 クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造