皆さんこんにちは。
SB C&Sの佐藤梨花です。
「Java17機能紹介ブログ」の第2弾となります。今回もおすすめ機能と、どういった点が注目ポイントなのか解説させて頂きます。
前回の記事でも説明させて頂きましたが、Java17を対象とした理由は「SpringBoot3.0以降公式サポートのjavaバージョン」であるからです。またSpringBoot2.7は「2025年8月」に期限切れとなります。
もしも現在SpringBoot2.7以前のバージョンをお使いの方やSpringBootの導入を検討中の方がいらっしゃいましたら、本記事、および前回の記事を通して是非アップデートのご検討を頂ければ幸いです。また文末には先月開催した「SpringBootサポート切れ対策セミナー」の動画視聴リンクも貼らせて頂いておりますので、こちらもご確認頂きますと幸いです。
JEP 409:Sealed Classes
■概要
親クラスの継承先を明示的に宣言・制御することが可能になります。
Java16以前では継承先の制限は【privateやprotectedによる制限】でしたが、この制限方法は「多くのクラスが必要とする要件以上の機能」でした。具体的に、以下のような経験がおありの方も多いと思います。
- データ構造的にクラスを分けたほうが良さそうだったけれど、元クラスをprivateで作成しているので、変更による影響を考慮しラッパークラスで対応を行った
※特に改修を重ねるとこのような対応が増えるのではないでしょうか
- protectedで作成したが、実際には同階層内の1~2クラスしか継承を行っていない
またこれは個人的な感想ですが、「protectedな親クラスを継承している子クラスが、親クラスのソースコードを見ただけでは判断出来ない」というのも気になっている点でした。呼び出し元等でのチェックではなく、一覧のようにコード上で確認出来た方が、継承数によっては可読性が高いのではと考えていました。
このような問題を解決するためのアプローチとして追加されたのが、「Sealed Classes」になります。
■実装方法
- クラス(もしくはインスタンス)に対し「sealed」修飾子を付与する。
例)abstract sealed class Root { ...
- 「permits」により継承を許可するクラスを定義する。
許可対象が複数存在する場合は「,」を使用し列挙する。
例)abstract sealed class Root permits A, B, C {
permitsを省略した場合、sealed宣言されたスーパークラスのソースファイル内でスーパークラスを直接継承するクラスを自動で許可対象とします。
例)
abstract sealed class Root { ...
final class A extends Root { ... }
final class B extends Root { ... }
final class C extends Root { ... }
}
⇒A,B,Cの3クラスがpermits対象となる
※制約※
permitsにより許可されるサブクラスはsealed宣言されたスーパークラスの「近く」になければいけない。
「近く」とは、
・同じモジュール内 (スーパークラスが名前付きモジュール内にある場合)
・同じモジュール内 パッケージ (スーパークラスが名前のないモジュールにある場合)
である。
- 大本のsealedクラスを継承したサブクラスに対し、 それ以降の継承制限をどうすを示す修飾子を付与する。
設定可能な修飾子は以下の3つです。
・final:それ以降の継承を不許可
・sealed:サブクラスを制限
・non-sealed:サブクラスを制限しない
例)
package com.example.geometry;
public abstract sealed class Shape permits Circle, Rectangle, Square, WeirdShape{ ... }
public final class Circle extends Shape { ... } // finalのため以降の継承不可
public sealed class Rectangle extends Shape permits TransparentRectangle, FilledRectangle { ... } // 「sealed」が設定されているため、親クラスと同じように「permits」で継承許可クラスを宣言
public final class TransparentRectangle extends Rectangle { ... }
public final class FilledRectangle extends Rectangle { ... }
public final class Square extends Shape { ... }
public non-sealed class WeirdShape extends Shape { ... } // 自由に継承が可能
この例を見て頂くと、親クラスで許可先を宣言することでこれまでの単純な継承先制限よりも可読性が上がり、また柔軟な設定が可能になっているのが見て取れます。データ構造をしっかりとソースコード化出来るのも良いですね。
■recordクラスとの相性
Recordは暗黙的にfinalであるため、RecordのSealedされた階層は簡潔に記述できます。
またSealedされたクラスとRecordの組み合わせは、代数データ型と呼ばれることもあり、
・ Recordでは積の型:
「かつ」の型。
例)Recordの「Employee」クラスは「IDを持つ」且つ「名前を持つ」且つ「入社日を持つ」型である
・ Sealedされたクラスでは和の型:
「または」の型。
例)「Employee」クラスを継承した「RegularEmployee」クラス(正社員)、「PartEmployee」クラス(パート社員)が存在する場合、「Employee」クラスは「RegularEmployee」または「PartEmployee」である
を表現できます。
代数データ型はPython等のスクリプト言語でよく利用されますが、代数データ型を利用することによりデータが構造化され、直感的に操作可能になります。
まとめ
今回は「Sealed Classes」について解説しました。いかがでしょうか?
これまでの厳格さ故の使い辛さを緩和し、データ構造に着目した新機能ということで、Javaの可能性が更に広がる機能だと感じました。プログラマが感じている不満を積極的に解消してくれるJavaの今後の進化にも期待が高まります!
そして冒頭にも紹介しましたが、「Java17」は「SpringBoot3.0」以降で正式サポートとなります。SpringBoot2.7のサポート切れ間近(実は2.7の延長により3.0が先に期限切れとなります...)ということで、この機会に是非アップグレードを検討してみてはいかがでしょうか。
DevOps関連情報はこちらから!
著者紹介
SB C&S株式会社
テクニカルマーケティングセンター
佐藤 梨花
勤怠管理システムの開発(使用言語:Java)に約8年間従事。
現在はエンジニア時の経験を活かしたDevOpsやDX推進のプリセールスとして業務に精励しています。