前回はrecordのコンパクトコンストラクタやカスタムメソッドを学びました。
今回は「instanceofパターンマッチング」と「switch式」をさらに深掘りします。 以前基本を学んだ内容ですが、Sealed Classesとの組み合わせや when ガード条件など、より実践的な使い方を解説します。
パターンマッチングとswitch式の復習
以前学んだ内容を振り返ります。
// 従来のswitch文
switch (day) {
case "月曜日":
result = "週の始まり";
break;
default:
result = "その他";
}
// switch式(Java 14+): 値を返せる、breakが不要
String result = switch (day) {
case "月曜日" -> "週の始まり";
default -> "その他";
};
今回はさらに「型によるパターンマッチング」「when ガード条件」「Sealed Classesとの組み合わせ」を学びます。
サンプルコード(switch式の基本とアロー構文)
ファイル作成
「Java45」フォルダに SwitchExprTest.java という名前でファイルを作成します

コード入力
以下のコードを入力します。
public class SwitchExprTest {
enum Season { SPRING, SUMMER, AUTUMN, WINTER }
public static void main(String[] args) {
// switch式で値を返す
for (Season season : Season.values()) {
String description = switch (season) {
case SPRING -> "桜が咲く季節";
case SUMMER -> "海水浴の季節";
case AUTUMN -> "紅葉の季節";
case WINTER -> "雪の季節";
};
System.out.println(season + ": " + description);
}
// 複数のcaseをまとめる
System.out.println("\n--- 季節グループ ---");
for (Season season : Season.values()) {
String group = switch (season) {
case SPRING, SUMMER -> "暖かい季節";
case AUTUMN, WINTER -> "寒い季節";
};
System.out.println(season + ": " + group);
}
// switch式でブロックを使い、yieldで値を返す
System.out.println("\n--- 月から季節へ ---");
for (int month = 1; month <= 12; month++) {
String season = switch (month) {
case 3, 4, 5 -> "春";
case 6, 7, 8 -> "夏";
case 9, 10, 11 -> "秋";
case 12, 1, 2 -> "冬";
default -> {
System.out.println("不正な月: " + month);
yield "不明";
}
};
System.out.printf("%2d月: %s%n", month, season);
}
}
}

コンパイル
javac SwitchExprTest.java

実行
java SwitchExprTest
実行結果は次のようになります。
SPRING: 桜が咲く季節
SUMMER: 海水浴の季節
AUTUMN: 紅葉の季節
WINTER: 雪の季節
--- 季節グループ ---
SPRING: 暖かい季節
SUMMER: 暖かい季節
AUTUMN: 寒い季節
WINTER: 寒い季節
--- 月から季節へ ---
1月: 冬
2月: 冬
3月: 春
4月: 春
5月: 春
6月: 夏
7月: 夏
8月: 夏
9月: 秋
10月: 秋
11月: 秋
12月: 冬

サンプルコード(Sealed Classes + record + switch式)
ファイル作成
「Java45」フォルダに Expr.java、ExprTest.java という名前でファイルを作成します

コード入力
Expr.java に以下のコードを入力します。
// 数式を表すSealed Interface
public sealed interface Expr permits
Expr.Num, Expr.Add, Expr.Sub, Expr.Mul, Expr.Div {
record Num(double value) implements Expr {}
record Add(Expr left, Expr right) implements Expr {}
record Sub(Expr left, Expr right) implements Expr {}
record Mul(Expr left, Expr right) implements Expr {}
record Div(Expr left, Expr right) implements Expr {}
}

ExprTest.java に以下のコードを入力します。
public class ExprTest {
// switch式で式を評価する
static double eval(Expr expr) {
return switch (expr) {
case Expr.Num n -> n.value();
case Expr.Add a -> eval(a.left()) + eval(a.right());
case Expr.Sub s -> eval(s.left()) - eval(s.right());
case Expr.Mul m -> eval(m.left()) * eval(m.right());
case Expr.Div d -> {
double right = eval(d.right());
if (right == 0) throw new ArithmeticException("0で割ることはできません");
yield eval(d.left()) / right;
}
};
}
// switch式で式を文字列化する
static String stringify(Expr expr) {
return switch (expr) {
case Expr.Num n -> String.valueOf(n.value());
case Expr.Add a -> "(" + stringify(a.left()) + " + " + stringify(a.right()) + ")";
case Expr.Sub s -> "(" + stringify(s.left()) + " - " + stringify(s.right()) + ")";
case Expr.Mul m -> "(" + stringify(m.left()) + " * " + stringify(m.right()) + ")";
case Expr.Div d -> "(" + stringify(d.left()) + " / " + stringify(d.right()) + ")";
};
}
public static void main(String[] args) {
// (3 + 4) * 2
var expr1 = new Expr.Mul(
new Expr.Add(new Expr.Num(3), new Expr.Num(4)),
new Expr.Num(2)
);
// 10 / (5 - 3)
var expr2 = new Expr.Div(
new Expr.Num(10),
new Expr.Sub(new Expr.Num(5), new Expr.Num(3))
);
System.out.println(stringify(expr1) + " = " + eval(expr1));
System.out.println(stringify(expr2) + " = " + eval(expr2));
// whenガード条件のテスト: 0除算
var expr3 = new Expr.Div(new Expr.Num(10), new Expr.Num(0));
try {
eval(expr3);
} catch (ArithmeticException e) {
System.out.println("エラー: " + e.getMessage());
}
}
}

コンパイル
javac Expr.java ExprTest.java

実行
java ExprTest
実行結果は次のようになります。
((3.0 + 4.0) * 2.0) = 14.0
(10.0 / (5.0 - 3.0)) = 5.0
エラー: 0で割ることはできません

コードの解説
switch式はswitch文と異なり「値を返せる」点が特徴です。-> のアロー構文を使うと break が不要で、各 case から直接値が返ります。case SPRING, SUMMER -> のように複数の値をカンマで並べてまとめることもできます。
ブロックを使う場合は yield で値を返します。break の代わりが yield です。default ブロックの中で yield "不明" とすることで、ブロック内でも値を返せます。
Expr の例は「数式ツリー」と呼ばれるデータ構造です。Num・Add・Sub・Mul・Div をSealed Interfaceとrecordで表現し、switch式のパターンマッチングで再帰的に評価しています。case Expr.Add a -> のように型変数を受け取り、a.left() や a.right() でフィールドにアクセスしています。
Div の処理ではブロックを使い、eval(d.right()) で右辺を先に計算してから0チェックを行い、問題なければ yield で除算結果を返します。Sealed Interfaceを使っているため、すべての case を網羅しているかコンパイラが確認してくれます。
まとめ
switch式はswitch文と異なり値を返せ、-> アロー構文で break が不要になります。複数の case をまとめたり、ブロック内では yield で値を返したりできます。Sealed Classes・record・パターンマッチングを組み合わせることで、型によって処理を分岐させる設計がコンパイラのサポートを受けながらシンプルに書けます。デコンストラクションパターンと when ガード条件を活用することで、より表現力の高いコードが実現できます。
2026/08/05(水) ぱんまる


コメント