【Java】シールクラスとは

## はじめに

  資格勉強を進める中で、シールクラスを学ぶ機会がありました。

  新しく追加されたばかりの言語仕様らしく、キャッチアップも兼ねて投稿させていただきます。
  
  ※誤りありましたら、ご指摘いただけますと幸いです。

## 想定読者
  初学者の方  
  普段Javaを使用している方

## シールクラスとは  
  シールクラスは、Java 17で正式に追加された新しいクラスの型です。
  
  シールクラスを使用すると、**クラスの継承階層を制御する** ことができます。具体的には、クラスの継承先、またはインターフェースの継承、実装先を限定することができます。

  簡単にまとめると、Aというクラスをシールクラスとした時に、AクラスはBクラス、Cクラス、Dクラスのみ継承を許可する、という指定ができるということになります。


## 実際の使い方
  実際の使い方を見ていきましょう。


```java:java
public sealed class Shape permits Circle, Rectangle, Triangle {
    //ここでは実装内容は省略
}
```

  これで、Shapeクラスはシールクラスになりました。
  シールクラスに指定する場合は、クラス名の前に **sealed** をつける必要があります。

  また、クラス名の後に、**permits 継承を許可するクラス名** と続けることで、継承を許可するクラスを指定できます。

  permitsで指定されたクラスがシールクラスを継承していないと、コンパイルエラーが発生します。

## サブクラスの制約
  継承されるサブクラスには、以下のいずれかのキーワードを使用して定義する必要があります。

  **① final**  
  →このサブクラスは固定クラスとして定義され、さらに継承されることはありません。

  **② sealed**  
  →このサブクラスもシールクラスとして定義され、そのサブクラスを制限します。

  **③ non-sealed**  
  →このサブクラスは制限なく継承可能で、通常のクラスと同様に振る舞います。

  実際の例を下記コードで見てみましょう。

```java:java
// シールクラス
public sealed class Shape permits Circle, Rectangle, Triangle {
    private final String color;

    public Shape(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }
}

// 固定クラス（サブクラスとしての継承を許可しない）
public final class Circle extends Shape {
    private final double radius;

    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }
}

// シールクラス（さらに制限する場合）
public sealed class Rectangle extends Shape permits Square {
    private final double length;
    private final double width;

    public Rectangle(String color, double length, double width) {
        super(color);
        this.length = length;
        this.width = width;
    }

    public double getLength() {
        return length;
    }

    public double getWidth() {
        return width;
    }
}

// 非シールクラス（通常の継承が可能）
public non-sealed class Triangle extends Shape {
    private final double base;
    private final double height;

    public Triangle(String color, double base, double height) {
        super(color);
        this.base = base;
        this.height = height;
    }

    public double getBase() {
        return base;
    }

    public double getHeight() {
        return height;
    }
}

// RectangleのサブクラスとしてのSquare
public final class Square extends Rectangle {
    public Square(String color, double side) {
        super(color, side, side);
    }
}


```

  **final** で指定されたCircleクラスは、継承が禁止されます。

  **sealed** で指定されたRectangleクラスは、さらにpermitsでSquareクラスの継承を許可しています。実際にコード内一番下では、Rectangleクラスを継承したSquareクラスを定義しています。

  **non-sealed** で指定されたTriangleは、通常のクラスと同様の振る舞いをします。他クラスから自由に継承することが可能です。

  #### 注意点

  permitsで指定できるクラスは **同一パッケージ内のみ** となっています。
  別パッケージのクラスをpermitsで指定できないので、注意してください。

  また、シールクラスと同一ソースファイル内に、permitsで継承を許可するクラスを記述する際には、下記のように、 **permits以下を省略** することができます。

```java:java
// シールクラス
public sealed class Shape {
}

// 最終クラス（サブクラスとしての継承を許可しない）
public final class Circle extends Shape {
    
}

// シールクラス（さらに制限する場合）
public sealed class Rectangle extends Shape {
    
}

// 非シールクラス（通常の継承が可能）
public non-sealed class Triangle extends Shape {
    
}

// RectangleのサブクラスとしてのSquare
public final class Square extends Rectangle {
    
}
```
  先程使用したコードですが、同一ソースファイル内に継承先のクラスを定義する際は省略できていることが分かります。

  ただ、基本的には同一ソースファイル内に複数クラス定義することはまれかと思いますので、使用頻度は少ないかと思われます。

## インタフェースもシールできる
  インタフェースもシール化することで、サブインタフェースや、実装クラスを制限することが可能です。

  しかし、インタフェースの性質上、final指定はできないため、サブインタフェースには、**sealed** か **non-sealed** が指定されることになります。


## まとめ
  シールクラスには、不要な継承を制限するほか、継承されていない場合はエラーを発生させる等、、実装の意図を伝える役割もあるように感じました。そういう意味では抽象クラスとも似ているかもしれません。

  導入されてから時間も経っておらず、どういう使われ方をされるのか分からない部分もまだまだありますが、キャッチアップを続けていきたいです。