GMOアドマーケティングのT.Nです。
Java 17でSealed ClassesがPreviewからStandardになりました。
弊社でも先日一部のプロダクトをJava 17にバージョンアップしたので、
今後Sealed Classesを活用していけるように記事にまとめました。
今回の記事は全体的にJEP 409 を参考にしています。
Sealed Classesとは
簡単に説明すると、extends、implementsできるクラスを制限するための仕組みです。
sealed、non-sealed、permitsという新しい文法を使って実現できます。
Sealed Classesを活用することで、
クラスの階層でドメイン知識を表現しやすくなります。
sealedをつけたクラス、インターフェースは、
permitsで指定したクラス、インターフェースでしかextends、implementsできないようになります。
non-sealedをつけたクラス、インターフェースは、
どのクラス、インターフェースからでもextends、implementsできるようになります。
ソースコードで説明すると以下のようになります。
(ReeMo、AkaNe、GMO SSPは弊社のプロダクトです。)
抽象クラスの場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public sealed class Product permits ReeMo, AkaNe, GmoSsp { // sealedを指定したクラス。 // permitsで、継承できるクラスをReeMo、AkaNe、GmoSspに制限している。 } public non-sealed class ReeMo extends Product { // permitsで指定されたReeMo。 // non-sealedで他のクラスが継承できるようにしている。 } public final class AkaNe extends Product { // permitsで指定されたAkaNe。 // finalで他のクラスが継承できないようにしている。 } public sealed class GmoSsp extends Product permits Batch { // permitsで指定されたGmoSsp。 // sealedを指定して、継承クラスをBatchに制限している。 } public final class Batch extends GmoSsp { // GmoSspのpermitsで指定されたクラス。 // finalで他のクラスが継承できないようにしている。 } |
インターフェースの場合
1 2 3 4 5 6 7 8 9 |
public sealed interface Bid permits ReeMo { // sealedを指定したインターフェース。 // permitsで、実装できるクラスをReeMoに制限している。 } public final class ReeMo implements Bid { // permitsで指定されたReeMo。 // finalで他のクラスが継承できないようにしている。 } |
以下のように内部クラスで定義した場合は、permitsを省略することができます。
1 2 3 4 5 6 7 8 9 10 11 12 |
public sealed class Product { public static non-sealed class ReeMo extends Product { } public static final class AkaNe extends Product { } public static sealed class GmoSsp extends Product { public static final class Batch extends GmoSsp { } } } |
以下のようにrecordにも適用することができます。
1 2 3 4 5 |
public sealed interface Bid permits ReeMo { } public record ReeMo() implements Bid { } |
recordはfinalであるため、sealedやnon-sealedを指定することはできません。
Sealed Classesのルール
Sealed Classesのルールをまとめました。以下のルール通りに実装しないとコンパイルエラーになります。
- permitsに指定されたクラスは、sealedクラスを必ずextends、implementsする必要がある。
- sealedクラスとpermitsで指定されたクラスは、同じpackage、またはmoduleに存在する必要がある。
- permitsで指定されたクラスは、sealedクラスを直接継承する必要がある。
- permitsで指定されたクラスでは、sealed、non-sealed、finalのいずれかを指定する必要がある。
Reflection APIについて
Sealed Classesの追加に伴い、Reflection APIに以下のメソッドが追加されました。
- Class<?> getPermittedSubclasses()
- boolean isSealed()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// [class products.GmoSsp, class products.AkaNe, class products.ReeMo] Arrays.toString(Product.class.getPermittedSubclasses()); // null Arrays.toString(ReeMo.class.getPermittedSubclasses()); // null Arrays.toString(AkaNe.class.getPermittedSubclasses()); // null Arrays.toString(GmoSsp.class.getPermittedSubclasses()); // null Arrays.toString(Batch.class.getPermittedSubclasses()); // true Product.class.isSealed(); // false ReeMo.class.isSealed(); // false AkaNe.class.isSealed(); // false GmoSsp.class.isSealed(); // false Batch.class.isSealed(); |
最初にsealedがつけられた一番上の階層のクラスだけ、
getPermittedSubclassesの結果を取得でき、isSealedもtrueになりました。
まとめ
今回の記事でSealed Classesの仕様について確認することができました。Sealed Classesを活用することで、
設計、実装の意図をソースコードで表現しやすくなると思うので、
今後Sealed Classesが普及していくと良いです。
明日はT.Mさんによる「マネージャーのキャリアパス」です。
引き続き、GMOアドマーケティング Advent Calendar 2021 をお楽しみください!
■エンジニア採用ページ ~福利厚生や各種制度のご案内はこちら~
https://note.gmo-ap.jp/n/n02cbeb6edb0d
■noteページ ~ブログや採用、イベント情報を公開中!~
https://note.gmo-ap.jp/