エンターテイメント!!

遊戯王好きのJavaエンジニアのブログ。バーニングソウルを会得する特訓中。

【Java】Java15先取り調査 JEP 360: Sealed Classes (Preview)

JEP

JEP 360: Sealed Classes (Preview)

内容

簡単に言うと、継承先を限定することができるクラスやインタフェースを作れるらしい。

これができる背景には、目的にそぐわない継承やインタフェースの実装が乱立していた事実があるのかもしれない。※個人の予想です。

コード的には、下記のような記載になる。

sealed class Shape permits Circle, Rectangle, Square { }

目新しい違いは、sealedとpermits。
sealedで、今回の機能を使うことを宣言している。
そして、継承を許可するクラスは、permitsで宣言。
例の内容だと、Circle, Rectangle, Squareが継承を許可されたことになる。

調査

まずは、載ってるサンプルを試す。

sealed class Shape permits Circle, Rectangle, Square { }

class Circle    extends Shape { }
class Rectangle extends Shape { }
class Square    extends Shape { }

javaファイル作ったので、コンパイル
preview版なので、--enable-previewをつける。
※本当はコンパイルしようとしたら指摘されて気づいた。。。

$ javac --enable-preview -source 15 JEP360.java 
JEP360.java:3: エラー: sealed、non-sealedまたはfinal修飾子が必要です
class Circle    extends Shape { }
^
JEP360.java:4: エラー: sealed、non-sealedまたはfinal修飾子が必要です
class Rectangle extends Shape { }
^
JEP360.java:5: エラー: sealed、non-sealedまたはfinal修飾子が必要です
class Square    extends Shape { }

なんか、final句つけないとダメらしい。。。
つまり、1世代しか生き残れない親クラスってわけか。
継承できる先を指定しているので、たしかに、そうだなって、思った。

final句つけて、再コンパイル

sealed class Shape permits Circle, Rectangle, Square { }

final class Circle    extends Shape { }
final class Rectangle extends Shape { }
final class Square    extends Shape { }
$ javac --enable-preview -source 15 JEP360.java 

通った。。。
でも、動作を確認できないから、動作確認ようのクラスを追加しないとダメだね。。。

とりあえず作ったサンプルコード!

sealed class Shape permits Circle, Rectangle, Square { }

final class Circle    extends Shape {
  public int center() {
    return 0;
  }
}
final class Rectangle extends Shape {
  public int length() {
    return 1;
  }
}
final class Square    extends Shape {
  public int side() {
    return 2;
  }
}

public class JEP360 {
  public static void main(String[] args) {
    Shape s = new Circle();
    System.out.println(JEP360.getCenter(s));
  }
  
  public static int getCenter(Shape shape) {
    if (shape instanceof Circle c) {
      return c.center();
    } else if (shape instanceof Rectangle r) {
        return r.length();
    } else if (shape instanceof Square s) {
        return s.side();
    }
    
    return -1;
  }
}

そんでもって実行。
期待値としては、Circleクラスを渡しているので、0が返ってきて欲しい。

$ javac --enable-preview -source 15 JEP360.java 
$ java --enable-preview JEP360
0

ちゃんと返ってきたね。

指定以外のクラスで継承するとどうなるのか?

とりあえずサンプルコード

sealed class Shape permits Circle, Rectangle, Square { }

final class Circle    extends Shape {
  public int center() {
    return 0;
  }
}
final class Rectangle extends Shape {
  public int length() {
    return 1;
  }
}
final class Square    extends Shape {
  public int side() {
    return 2;
  }
}

final class Test extends Shape {
  
}

あらたにTestというクラスを追加した。
このクラスは、Shapeを継承しているけど、Shape側のpermitsには含まれていない。

たぶん、コンパイル時にエラーで弾かれると思う。

$ javac --enable-preview -source 15 JEP360.java 
JEP360.java:19: エラー: クラスはシール・クラスShapeを拡張できません
final class Test extends Shape {
      ^

当たり!!
やっぱり、コンパイル時に弾かれるよね。
正解したから、何かご褒美が欲しい。

permitsに存在しないクラスを指定するとどうなるのか?

とりあえず、サンプルコード

sealed class Shape permits Circle, Rectangle, Square, Unknown { }

final class Circle    extends Shape {
  public int center() {
    return 0;
  }
}
final class Rectangle extends Shape {
  public int length() {
    return 1;
  }
}
final class Square    extends Shape {
  public int side() {
    return 2;
  }
}

Unknown が存在しないクラス。
その他は定義済み。

期待値としては、そのままビルド通っていいのでは?って思ってる。

$ javac --enable-preview -source 15 JEP360.java 
JEP360.java:1: エラー: シンボルを見つけられません
sealed class Shape permits Circle, Rectangle, Square, Unknown { }
                                                      ^
  シンボル: クラス Unknown
JEP360.java:1: エラー: 無効なpermits句
sealed class Shape permits Circle, Rectangle, Square, Unknown { }
                           ^
  (スーパータイプUnknownへの参照が不正です)

なんだと。。。
ビルド時にクラスの存在も確認しているのか。。。

感想

クラスをグルーピングできるようになるって理解でよいだろうか?
使う側で不用意な拡張とかされないようになるから、FW側を安全に使うようになる理解でいるが、どうだろう?

若干、疑問に思っているのは、オープン・クローズドの原則と相反する気がするのだが、気のせいだろうか?
なぜ入ったのか、経緯をよく理解できてないせいか、どうにも腑に落ちない。

今年はイベントがほとんどないから、理解があっているのか確かめる場が無いのが痛い。。。