S-JIS[2022-09-25/2023-09-23] 変更履歴
Java19〜20のレコードのパターンマッチング(プレビュー版)について。
|
|
2022/9/21にリリースされた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の後にレコード名を書き、その直後に丸括弧で囲んでフィールドから受け取る変数を書く。
この変数(の個数とデータ型)はレコードのフィールドと一致している必要がある。(変数名が一致する必要は無い)
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の例。
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; }
↓
Example.java:65: エラー: 既存の一致バインディングを再定義することはできません case Record2(int i): ^
※「case Record1(int i), Record2(int i):
」も駄目
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)) { ^