S-JIS[2022-09-25/2023-09-23] 変更履歴

レコードパターン(Java19〜20)

Java19〜20のレコードのパターンマッチング(プレビュー版)について。


概要

2022/9/21にリリースされたJava19で、プレビュー版としてinstanceofswitch文/式でパターンマッチングによるレコードの分解(フィールドの抽出)が出来るようになった。

record Point(int x, int y) {}
    static void printPoint(Object o) {
        if (o instanceof Point(var x, var y)) {
            System.out.printf("(%d, %d)%n", x, y);
        }

        switch(o) {
        case Point(var x, var y):
            System.out.printf("(%d, %d)%n", x, y);
            break;
        default:
            break;
        }
    }

instanceofやcaseの後にレコード名を書き、その直後に丸括弧で囲んでフィールドから受け取る変数を書く。
この変数(の個数とデータ型)はレコードのフィールドと一致している必要がある。(変数名が一致する必要は無い)

Java20ではfor each文でパターンマッチングが出来るようになった。[2023-03-22]

レコードのパターンマッチングはJava21で正式機能になったが、for each文には対応していない。[2023-09-23]


レコードのパターンマッチングはJava19〜20ではプレビュー版の機能なので、この機能を使いたい場合はコンパイル時にjavacコマンドに--enable-previewを付ける必要があり、
また、実行時にjavaコマンドに--enable-previewを付ける必要がある。
JShellで試す場合もjshellコマンドに--enable-previewを付ける。

> javac --enable-preview --release 19 Example.java
> java --enable-preview Example

instanceofの例

instanceofの例。

record Point(int x, int y) {}
同等の例 備考
if (o instanceof Point(int x, int y)) {
  System.out.printf("(%d, %d)%n", x, y);
}
 
if (o instanceof Point p) {
  System.out.printf("(%d, %d)%n", p.x(), p.y());
}
 
 
double len = (o instanceof Point(int x, int y)) ?
  Math.sqrt(x*x + y*y) : 0;
 
double len = (o instanceof Point p) ?
  Math.sqrt(p.x()*p.x() + p.y()*p.y()) : 0;
 
条件演算子(三項演算子)でも使用可能。

switchの例

switchの例。

record Record1(int n) {}
record Record2(int n) {}
    static void test(Object o) {
        switch (o) {
        case Record1(int i):
            System.out.println("rec1=" + i);
            break;
        case Record2(int i):
            System.out.println("rec2=" + i);
            break;
        default:
            System.out.println("default");
            break;
        }
    }

フィールドの抽出に使用する変数名は、異なるcaseであれば同じ変数名を使用可能。
(通常、switchブロックの中では、(異なるcaseの下であっても)同じ名前のローカル変数を複数定義できない(コンパイルエラーになる))


ただし、フィールドの抽出に使う変数が同じ変数名で同じデータ型であっても、fall throughさせることは出来ない。

        switch (o) {
        case Record1(int i):
        case Record2(int i): // コンパイルエラー
            System.out.println("rec=" + i);
            break;
        default:
            System.out.println("default");
            break;
        }

Example.java:65: エラー: 既存の一致バインディングを再定義することはできません
                case Record2(int i):
                     ^

※「case Record1(int i), Record2(int i):」も駄目


for eachの例

Java20では、for文(拡張for文)でも レコードのパターンマッチングが出来るようになった。[2023-03-22]

	var list = List.of(new Point(1, 2), new Point(3, 4));
	for (Point(var x, var y) : list) {
		System.out.printf("(%d, %d)%n", x, y);
	}

レコードのパターンマッチングの例。
(instanceof・switch等で共通)


データ型

レコードのフィールドを受け取る変数のデータ型は、フィールドの型と一致している必要がある。
(変数名が一致している必要は無い)

record RecordInt1(int n) {}
    static void test(Object o) {
        switch (o) {
        case RecordInt1(int i):
            System.out.println("rec1=" + i);
            break;
        default:
            break;
        }
    }

レコードのフィールドをvarで受け取ることが出来る。

        switch (o) {
        case RecordInt1(var i):
            System.out.println("rec1=" + i);
            break;
        default:
            break;
        }

varと直接のデータ型指定は混在させることが出来る。
ラムダ式では混在できないが)

record RecordInt2(int n, int m) {}
        switch (o) {
        case RecordInt2(var i, int j):
            System.out.println(i + j);
            break;
        default:
            break;
        }

varを使わない場合、フィールドがプリミティブ型なら、全く同じデータ型を指定しなければならない。
例えばレコードのフィールドがintなのにlongで受けることは出来ない。(ラッパークラスも駄目)(コンパイルエラーになる)

        switch (o) {
        case RecordInt1(long i): // コンパイルエラー
            System.out.println("rec1=" + i);
            break;
        }

Example.java:36: エラー: 不適合な型: pattern of type long is not applicable at int
                case RecordInt1(long i):
                                ^

継承関係にあるクラス(やインターフェース)は指定可能で、期待通りマッチする。
(明らかに代入できない(継承関係にない)クラスを指定するとコンパイルエラーになる)

record RecordS(CharSequence s) {}
        switch (o) {
//×    case RecordS(Integer s): // コンパイルエラー
        case RecordS(String s):  // OK
            System.out.println("string=" + s);
            break;
        default:
            System.out.println("default");
            break;
        }

レコードのネスト

レコードの中にレコードがある場合は、内側のレコードを指定してフィールドを抽出できる。

record Point(int x, int y) {}
record Rectangle(Point leftTop, Point bottomRight) {}
    if (o instanceof Rectangle(Point(var x1, var y1), Point(var x2, var y2))) {
        System.out.printf("(%d, %d)-(%d, %d)%n", x1, y1, x2, y2);
    }
// 同等の書き方の例
    if (o instanceof Rectangle r) {
        System.out.printf("(%d, %d)-(%d, %d)%n", r.leftTop().x(), r.leftTop().y(), r.bottomRight().x(), r.bottomRight().y());
    }
    if (o instanceof Rectangle(var p1, var p2)) {
        System.out.printf("(%d, %d)-(%d, %d)%n", p1.x(), p1.y(), p2.x(), p2.y());
    }

    // 一部をレコードのフィールドとして抽出し、一部をレコード自体として受け取ることも可能
    if (o instanceof Rectangle(Point(var x1, var y1), var p2)) {
        System.out.printf("(%d, %d)-(%d, %d)%n", x1, y1, p2.x(), p2.y());
    }

Java19では、レコードのフィールドレコード自身を受け取る変数も一緒に指定可能。

    if (o instanceof Rectangle(Point(var x1, var y1) p1, Point(var x2, var y2) p2) r) {
        System.out.printf("%s%n", r);
        System.out.printf("%s-%s%n", p1, p2);
        System.out.printf("(%d, %d)-(%d, %d)%n", x1, y1, x2, y2);
    }
    switch(o){
    case Rectangle(Point(var x1, var y1) p1, Point(var x2, var y2) p2) r:
        System.out.printf("%s%n", r);
        System.out.printf("%s-%s%n", p1, p2);
        System.out.printf("(%d, %d)-(%d, %d)%n", x1, y1, x2, y2);
        break;
    default:
        break;
    }

Java20では出来なくなった(コンパイルエラーになる)。[2023-09-23]


固定値

一部のフィールドに定数を指定して、その定数のときだけマッチするという書き方は出来ない。

record RecordInt2(int n, int m) {}
        switch (o) {
//×    case RecordInt2(123, int j): // 定数を書くとコンパイルエラー
        case RecordInt2(int i, int j) when i == 123: // OK
            System.out.println("rec.m=" + j);
            break;
        default:
            System.out.println("default");
            break;
        }

ジェネリクス

ジェネリクスが使われているレコードをパターンマッチングする例。

record Box<T>(T t) {}
        test(new Box<>("abc"));
        test(new Box<>(123));
    static void test(Box<Object> box) {
        switch (box) {
        case Box<Object>(String s):
            System.out.println("Box<Object>(String s)=" + s);
            break;
        case Box<Object>(Integer n):
            System.out.println("Box<Object>(Integer n)=" + n);
            break;
//×    case Box<>(String s):
//          System.out.println("Box<>(String s)=" + s);
//          break;
//×    case Box(String s):
//          System.out.println("Box(String s)=" + s);
//          break;
        default:
            break;
        }
    }

JEP405によれば、 「Box<Object> box」に対して以下のような式は適正らしいのだが、実際にJava19〜20でコンパイルするとエラーになる。

        if (box instanceof Box<String>(var s)) {
            System.out.println("Box<String>(var s)=" + s);
        }

Example.java:29: エラー: 不適合な型: Box<Object>をBox<String>に変換できません:
                if (box instanceof Box<String>(var s)) {
                    ^

JEP432(second preview)では、適正ではなくなった模様。[2023-09-23]


<?>」からのパターンマッチングも無理そう。

    static void testQ(Box<?> box) {
        if (box instanceof Box<String>(var s)) {
            System.out.println("Box<String>(var s)=" + s);
        }
    }

Example.java:42: エラー: Box<CAP#1>をBox<String>に安全にキャストできません
                if (box instanceof Box<String>(var s)) {
                    ^

プレビュー機能へ戻る / Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま