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)) {
^