1. 導入:なぜこの組み合わせが重要なのか?
Javaで開発をしていると、「特定のデータ構造を網羅的に処理したい」という場面に頻繁に出くわします。しかし、従来のクラス設計では、継承関係が複雑になりすぎたり、条件分岐で漏れが発生したりすることが課題でした。
そこで登場するのが、Java 17以降で導入された「Sealedクラス」と「Record型」の組み合わせです。これを使うことで、コンパイラが「処理の漏れ」を強制的にチェックしてくれるようになり、バグを未然に防ぐ堅牢なコードが書けるようになります。
2. 基礎知識:SealedクラスとRecord型とは?
まず、それぞれの役割を整理しましょう。
・Sealedクラス(封印されたクラス):
そのクラス(またはインターフェース)を「誰が継承できるか」を開発者が明示的に制限する機能です。これにより、サブクラスが予測不能に増えることを防ぎます。
・Record型(レコード):
データを保持することに特化した、簡潔なクラス定義です。不変(イミュータブル)なデータ構造を数行で作成でき、ボイラープレート(定型コード)を大幅に削減できます。
これらを組み合わせることで、「データ型が決まっており、かつその種類が制限されている」という、非常に安全な設計が可能になります。
3. 実装:パターンマッチングを活用した制御フロー
Sealedクラスで定義した型を、Javaの「switch式」で処理するのが現代Javaの鉄板パターンです。switch式では、すべてのサブクラスを網羅しているかをコンパイラが確認するため、漏れがあればビルドエラーにしてくれます。
4. サンプルプログラム
以下は、図形の面積を計算するプログラムです。コピーして動作を確認してみてください。
// 1. Sealedインターフェースで継承先を制限
sealed interface Shape permits Circle, Rectangle {}
// 2. データ構造をRecordで定義
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
public class Main {
public static void main(String[] args) {
Shape shape = new Circle(5.0);
System.out.println("面積: " + calculateArea(shape));
}
public static double calculateArea(Shape shape) {
// switch式によるパターンマッチング
// 全ての型を網羅しないとコンパイルエラーになるため非常に安全です
return switch (shape) {
case Circle c -> Math.PI c.radius() c.radius();
case Rectangle r -> r.width() r.height();
// defaultを記述する必要がないのが最大のメリットです
};
}
}
5. 応用・注意点:現場で役立つポイント
・網羅性の保証:
もし将来的に「Triangle(三角形)」を追加したくなった場合、ShapeインターフェースのpermitsにTriangleを追加します。すると、既存のswitch式で「Triangleのケースが処理されていない!」とコンパイラが即座に警告を出してくれます。これにより、修正漏れによる実行時エラーをゼロにできます。
・yieldの活用:
もしcase内で複雑な処理が必要な場合は、波括弧 { } を使い、最後に yield を記述することで値を返すことができます。
・注意点:
この機能はJava 17以降で完全に利用可能です。古いバージョンのJavaを使っているプロジェクトでは利用できないため、環境(JDKバージョン)を事前に確認しましょう。
堅牢なコードは、こうした「コンパイラに守ってもらう設計」から生まれます。ぜひ日々の開発に取り入れてみてください。

コメント