CodeGym /コース /JAVA 25 SELF /Record Patterns (Java 21+): 構文と例

Record Patterns (Java 21+): 構文と例

JAVA 25 SELF
レベル 65 , レッスン 3
使用可能

1. record クラスを掘り下げる

record patterns に入る前に、record クラスとは何かをおさらいしましょう。

Record はコンパクトで不変なクラスで、equalshashCodetoString が自動生成され、すべてのコンポーネントに対するゲッターも自動で用意されます。Record クラスは Java 16 で登場し、手書きの DTO にうんざりしていた開発者にとっての本当の贈り物となりました。

// 定番: 2 つのフィールドを持つ Point
record Point(int x, int y) {}

オブジェクトの生成とフィールドへのアクセス:

Point p = new Point(10, 20);
System.out.println(p.x()); // 10
System.out.println(p.y()); // 20
System.out.println(p);     // Point[x=10, y=20]

Record Patterns が必要な理由

Java 21 以前は、オブジェクトが特定の record かどうかを確認してそのフィールドを得るには、instanceof による型チェック、キャスト、ゲッター呼び出しと、手作業のコードを多く書く必要がありました。

Object obj = new Point(5, 7);
if (obj instanceof Point) {
    Point p = (Point) obj;
    int x = p.x();
    int y = p.y();
    System.out.println("x=" + x + ", y=" + y);
}

record が複雑または入れ子の場合、コードはすぐに「アンパックの森」になってしまいます。さらに、こうした判別を switch で使うのは以前は不便でした。

Record patterns を使うと、record クラスの値をパターンマッチングの中でそのままアンパックできます。これは instanceofswitch の両方で使え、コードはより短く、安全で、読みやすくなります。

2. record patterns の構文: 最も簡単な例

record Point(int x, int y) {}

Object obj = new Point(10, 20);

if (obj instanceof Point(int x, int y)) {
    System.out.println("x=" + x + ", y=" + y);
}

ここで何が起きているか

  • obj instanceof Point(int x, int y) — 型を確認しつつ、コンポーネントを変数 xy に即座にアンパックします。
  • 変数 xyif ブロックの内部でのみ有効です。

結果:

x=10, y=20

注意: objPoint でない、または null の場合、if ブロックは実行されず、変数は宣言されません。

3. switch での Record patterns: 簡潔さと安全性

record Point(int x, int y) {}
record Circle(Point center, int radius) {}

Object shape = new Point(3, 4);

switch (shape) {
    case Point(int x, int y) -> System.out.println("Point: x=" + x + ", y=" + y);
    case Circle(Point center, int r) -> System.out.println("Circle with radius " + r);
    default -> System.out.println("Unknown shape!");
}
  • casePoint(int x, int y) では、xy にすぐアクセスできます。
  • caseCircle(Point center, int r) では、centerr を参照できます。

結果:

Point: x=3, y=4

sealed 階層と組み合わせると、switch は網羅的になり、ケースの扱い漏れがあるとコンパイラが警告してくれます。

4. 入れ子の record patterns: アンパックの中でアンパック

record Point(int x, int y) {}
record Line(Point start, Point end) {}

Object obj = new Line(new Point(1, 2), new Point(3, 4));

if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
    System.out.println("Line from (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ")");
}

結果:

Line from (1,2) to (3,4)

木構造、グラフ、複雑なデータ構造を扱っていますか?入れ子のパターンは強力な味方です。

5. ガード条件 (when) を伴う record patterns

Object obj = new Point(0, 100);

if (obj instanceof Point(int x, int y) && x == 0) {
    System.out.println("Y 軸上の点: y=" + y);
}

switch では when を使います:

switch (obj) {
    case Point(int x, int y) when x == 0 -> System.out.println("Y 軸上: y=" + y);
    case Point(int x, int y) when y == 0 -> System.out.println("X 軸上: x=" + x);
    case Point(int x, int y) -> System.out.println("通常の点");
    default -> System.out.println("点ではない");
}

6. 実アプリケーションでの record patterns の活用

実用的な例として、幾何学図形を扱ってみます。

record Point(int x, int y) {}
record Circle(Point center, int radius) {}
record Rectangle(Point topLeft, int width, int height) {}
public static void printShapeInfo(Object shape) {
    switch (shape) {
        case Point(int x, int y) -> System.out.println("Point: (" + x + ", " + y + ")");
        case Circle(Point(int x, int y), int radius) ->
            System.out.println("Circle: center=(" + x + ", " + y + "), radius=" + radius);
        case Rectangle(Point(int x, int y), int width, int height) ->
            System.out.println("Rectangle: topLeft=(" + x + ", " + y + "), size=" + width + "x" + height);
        default -> System.out.println("Unknown shape");
    }
}

呼び出し例:

printShapeInfo(new Rectangle(new Point(5, 10), 20, 30));

結果:

Rectangle: topLeft=(5, 10), size=20x30

7. record patterns の制約と注意点

record クラス専用。 Record patterns は、本当に record クラスであるオブジェクトに対してのみ機能します。

class NotARecord {
    int a, b;
    NotARecord(int a, int b) { this.a = a; this.b = b; }
}

// エラー! record ではない
// if (obj instanceof NotARecord(int a, int b)) { ... }

コンポーネントの数と型が一致していること。 パターンは record クラスのコンポーネントに一致していなければなりません。

record Pair(int a, String b) {}

// エラー: 型が一致しない
// if (obj instanceof Pair(String a, int b)) { ... }

変数はブロック内でのみ有効。 パターンで宣言された名前は、該当の ifcase の本体でマッチが成功した場合にのみ参照できます。

8. 入れ子パターン: 木の例

sealed interface Expr permits NumberExpr, PlusExpr, MinusExpr {}

record NumberExpr(int value) implements Expr {}
record PlusExpr(Expr left, Expr right) implements Expr {}
record MinusExpr(Expr left, Expr right) implements Expr {}
int eval(Expr expr) {
    return switch (expr) {
        case NumberExpr(int value) -> value;
        case PlusExpr(Expr left, Expr right) -> eval(left) + eval(right);
        case MinusExpr(Expr left, Expr right) -> eval(left) - eval(right);
    };
}

使用例:

Expr e = new PlusExpr(new NumberExpr(7), new MinusExpr(new NumberExpr(10), new NumberExpr(3)));
System.out.println(eval(e)); // 7 + (10 - 3) = 14

ここでは NumberExpr(int value)PlusExpr(Expr left, Expr right)MinusExpr(Expr left, Expr right) をその場でアンパックしており、コードは簡潔で表現力豊かになります。

9. 表: record patterns あり/なしの pattern matching の比較

方法 手動の型キャスト record patterns を用いた pattern matching
型チェック
if (obj instanceof ...)
if (obj instanceof Point(int x, int y))
キャスト
(Point) obj
不要
フィールドへのアクセス
p.x(), p.y()
x, y
入れ子のアンパック 手作業のコードが多い 入れ子のパターン
switch での利用 不便/不可能 容易で簡潔

10. record patterns のよくある誤り

誤り 1: record でないクラスに record pattern を適用する。 通常のクラスを書いてからそれを record のように「アンパック」しようとすると、コンパイラは許可しません。Record patterns は本物の record クラスに対してのみ動作します。

誤り 2: コンポーネントの数または型が一致しない。 パターンは record のコンポーネント宣言に完全一致していなければなりません。例えば、record Pair(int a, String b) に対して Pair(int x, int y) と書くことはできません。

誤り 3: パターンの変数をブロック外で使おうとする。 パターンで宣言された名前は、対応する if/case の内部でのみ有効です。ブロック外ではそのような変数は存在しません。

誤り 4: 古い JDK で record patterns を使う。 サポートは Java 21 で導入されました。Java 17 以下では構文エラーになります。JDK のバージョンと IDE のサポートを確認してください。

誤り 5: 入れ子のパターンが深すぎる。 入れ子は強力ですが、不要に複雑化しないでください。構造が深すぎると可読性が低下し、ミスのリスクが高まります。

1
タスク
JAVA 25 SELF, レベル 65, レッスン 3
ロック未解除
幾何学エディタ:点の位置の説明 📍
幾何学エディタ:点の位置の説明 📍
1
タスク
JAVA 25 SELF, レベル 65, レッスン 3
ロック未解除
電卓の中核: 複雑な式の評価 🧠➕➖
電卓の中核: 複雑な式の評価 🧠➕➖
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION