Javaでは複数の定数をまとめて扱う型としてenum(列挙型)が用意されています。
これはこれで便利なのですが物足りないところもあって、それがSealedクラスなどを使うことで解決できるようになるので、解説します。
enum型
enumは状態やデータ区分を表すのによく使われます。
構文は次のようになります。
enum 型名 { 列挙1, 列挙2, ... }
例えば次のような状態を表すとします。
この状態を表すenumは次のようになります。それぞれの値は大文字で書くようにします。
enum State {
READY, RUNNING, SUSPENDED, TERMINATED
}
enumではそれぞれの値ごとに処理を行うということがよくあります。そこでswitchと相性がいいです。
State s = State.READY; switch (s) { case READY -> System.out.println("準備完了"); case RUNNING -> System.out.println("実行中"); case SUSPENDED-> System.out.println("停止中"); case TERMINATED-> System.out.println("終了"); }
switch文の場合はenum値が足りなくても大丈夫ですが、switch式では全てのenum値を処理しないとエラーになります。
このことを考えると、enum値すべての処理をするswitchは式にしておいたほうがいいですね。(Java 14以降)
var message = switch (current) { case READY -> "準備完了"; case RUNNING -> "実行中"; case SUSPENDED -> "停止中"; case TERMINATED -> "終了"; };
enumに値を持たせる
enum値それぞれに数値などを割り当てたい場合があります。そういう場合はフィールドとして定義することができます。 メソッドも定義できるのでgetterを用意するか、toStringメソッドをオーバーライドすることになりますね。 フィールドはprivate finalにしておきましょう。
enum State { READY("準備完了"), RUNNING("実行中"), SUSPENDED("停止中"), TERMINATED("終了"); private final String name; State(String name) { this.name = name; } public String getName() { return name; } @Override public String toString() { return name; } }
ただまあ、フィールドとコンストラクタとgetterを定義していくのめんどいので、Lombokを使いたくなります。
@Value enum State { READY(1, "準備完了"), RUNNING(2, "実行中"), SUSPENDED(3, "停止中"), TERMINATED(4, "終了"); int code; String name; @Override public String toString() { return name; } }
Sealedクラスを拡張enumとして使う
JavaのenumはCのenumよりは便利なのですが、パラメータを追加したいという使い方ができません。
例えばMouseEventというenumがあって、CLICKEDとMOVEDがあるんだけど座標も持たせたい、というような場合です。あとCLICKEDにはどのボタンかも持たせたい。
という場合にSealedクラスが使えます。実際に使うのはinterfaceになるけど。
enum Button { LEFT, MIDDLE, RIGHT } sealed interface Event { record Clicked(Button button, int x, int y) implements Event {} record Moved(int x, int y) implements Event {} }
sealedをつけたクラスやインタフェースでは、そのnestedクラスだけで継承が行えるようになります。nestedクラス以外で継承を行う場合にはpermitsで指定します。
sealed interface Event permits Clicked, Moved {} record Clicked(Button button, int x, int y) implements Event {} record Moved(int x, int y) implements Event {}
そして、Java 19でプレビューとして導入されたpattern matching for switchとrecord patternを利用すれば、enumのように扱えます。
Event e = new Event.Clicked(Button.RIGHT, 7, 3); var message = switch (e) { case Event.Clicked(var b, int x, int y) -> "%sボタンが%d,%dでクリックされた".formatted(b, x, y); case Event.Moved(int x, int y) -> "%d,%dに動いた".formatted(x, y); };
ここでEvent型はSealedなので、継承するクラスはClickedかMovedに限られるため、可能なすべての型をチェックしたことになります。
ということで、recordとsealedクラスとパターンマッチとswitch式を組み合わせると高機能enumのように使えるという話でした。