Java21(プレビュー版ではJava19)で、instanceofやswitch文/式でパターンマッチングによるレコードの分解(フィールドの抽出)が出来るようになった。
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の後にレコード名を書き、その直後に丸括弧で囲んでフィールドから受け取る変数を書く。
この変数(の個数とデータ型)はレコードのフィールドと一致している必要がある。(変数名が一致する必要は無い)
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の例。
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; }
↓
RecordPatternExample.java:22: エラー: パターンからの不正なfall-through case Record1(int i): ^ RecordPatternExample.java:23: エラー: パターンに対して不正なfall-through case Record2(int i): ^
「case Record1(int i), Record2(int i):
」も駄目。
RecordPatternExample.java:22: エラー: 既存の一致バインディングを再定義することはできません case Record1(int i), Record2(int i): ^
レコードのパターンマッチングの例。
(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: エラー: 不適合な型: 型longのパターンは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()); }
一部のフィールドに定数を指定して、その定数のときだけマッチするという書き方は出来ない。
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; }
ジェネリクスが使われているレコードをパターンマッチングする場合、基本的に型引数を除去してcaseに記述する。
record Box<T>(T t) {}
test(new Box<>("abc")); test(new Box<>(123));
static void test(Box<Object> box) { // switch switch (box) { case Box(String s): System.out.println("Box(String s)=" + s); break; case Box(Integer n): System.out.println("Box(Integer n)=" + n); break; default: break; } // instanceof if (box instanceof Box(String s)) { System.out.println("Box(String s)=" + s); } }
Box<Object> box |
Box<String> box |
Box<?> box |
|
case Box(String s): |
○ | ○ | ○ |
case Box<>(String s): |
× | × | × |
case Box<Object>(String s): |
○ | × | × |
case Box<String>(String s): |
× | ○ | × |
case Box<String>(var s): |
× | ○ | × |
case Box(Integer n): |
○ | × | ○ |