はじめに
JDK 21 でプレビュー公開された Unnamed Variables & Patterns が、JDK 22 で JEP 456 正式リリースされることになりました。プレビュー版からは特に機能の変更は入らずに正式リリースとなります。
Unnamed Variables & Patterns により、未使用の、変数宣言やネストされたパターンを _
文字で省略して書くことができるようになります。省略表記は、大きく以下の3種に大別されます。
- Unnamed variables (無名変数)
- ローカル変数宣言文のローカル変数名を
_
で省略表記 - つまり
int x
をint _
の様に記載(var _
も可)
- ローカル変数宣言文のローカル変数名を
- Unnamed pattern variables (無名パターン変数)
- 型パターンのパターン変数を
_
で宣言 - つまり
case RedBall red -> foo();
をcase RedBall _ -> foo();
の様に記載
- 型パターンのパターン変数を
- Unnamed pattern (無名パターン)
- レコードコンポーネントの型と名前の両方を
_
で宣言 - 無名型パターン
var _
と等価 - つまり
ColoredPoint(Point(int x, int y), Color c)
というパターンをColoredPoint(Point(int x, int y), _)
のように記載
- レコードコンポーネントの型と名前の両方を
最後の 「Unnamed pattern (無名パターン)」 がこのJEPのポイントで、最初の2つはおまけ程度と考えておけば良いと思います。
また、本JEPでは、Unnamed method parameters は対象外なので、メソッドの引数に無名変数を使うことは現状ではできません。
Unnamed variables (無名変数)
無名変数は以下で使用することができます。
- ブロック内のローカル変数宣言文
try
-with-resources文のリソース指定- 基本的な
for
ループのヘッダー - 拡張
for
ループのヘッダー - キャッチブロックの例外パラメータ
- ラムダ式の正規パラメータ
ブロック内のローカル変数宣言文
while (q.size() >= 3) { var x = q.remove(); var _ = q.remove(); // var y = q.remove(); var _ = q.remove(); // var z = q.remove(); ... new Point(x, 0) ... }
無名変数は名前を持たないので、同じブロック内で複数の無名変数を宣言することができます。
try
-with-resources文のリソース指定
// try (var acquiredContext = ScopedContext.acquire()) { try (var _ = ScopedContext.acquire()) { ... no use of acquired resource ... }
基本的な for
ループのヘッダー
// for (int i = 0, ignore = sideEffect(); i < 10; i++) { for (int i = 0, _ = sideEffect(); i < 10; i++) { ... }
拡張 for
ループのヘッダー
int total = 0; for (Order _ : orders) // for (Order order : orders) total++;
キャッチブロックの例外パラメータ
String s = ... try { int i = Integer.parseInt(s); ... i ... } catch (NumberFormatException _) { // } catch (NumberFormatException ignore) { System.out.println("Bad number: " + s); }
もちろん無名変数を複数利用することもできます。
try { ... } catch (Exception _) { ... } // catch (Exception ignore) catch (Throwable _) { ... } // catch (Throwable ignore)
ラムダ式の正規パラメータ
stream.collect(Collectors.toMap( String::toUpperCase, _ -> "NODATA")); // ignore -> "NODATA"));
Unnamed pattern variables (無名パターン変数)
型パターンに無名パターン変数を利用することができます。これは型パターンがトップレベルでも、レコードパターンの中にネストされていても利用できます。
以下のような3種の Ball
クラスがあった場合
sealed abstract class Ball permits RedBall, BlueBall, GreenBall { } final class RedBall extends Ball { } final class BlueBall extends Ball { } final class GreenBall extends Ball { }
パターン変数を省略して以下のように書くことができます。
switch (ball) { // パターン変数 `red`、`blue`、`green` は右辺で使われない case RedBall _ -> process(ball); // case RedBall red -> process(ball); case BlueBall _ -> process(ball); // case BlueBall blue -> process(ball); case GreenBall _ -> stopProcessing(); // case GreenBall green -> stopProcessing(); }
任意の Ball
と null
を保持できる Box
レコードがあった場合、
record Box<T extends Ball>(T content) { }
型パターンはネストされたパターンとして以下のようになります。
Box<? extends Ball> box = ... switch (box) { case Box(RedBall red) -> processBox(box); case Box(BlueBall blue) -> processBox(box); case Box(GreenBall green) -> stopProcessing(); case Box(var itsNull) -> pickAnotherBox(); }
このようなネストしたケースにおいてもパターン変数を右辺で使わない場合は、以下のように無名パターン変数を使うことができます(instanceof
演算子の型パターンについても同様です)。
switch (box) { case Box(RedBall _) -> processBox(box); case Box(BlueBall _) -> processBox(box); case Box(GreenBall _) -> stopProcessing(); case Box(var _) -> pickAnotherBox(); }
加えて、スイッチラベルの文法が、ケースパターンを連続して記述できるよう拡張し、以下のように書くことができるようになりました(この例は、無名パターンによりさらに簡略化できることを後述します)。
switch (box) { case Box(RedBall _), Box(BlueBall _) -> processBox(box); case Box(GreenBall _) -> stopProcessing(); case Box(var _) -> pickAnotherBox(); }
スイッチラベルの文法は以下のように変更されています。
SwitchLabel: case CaseConstant {, CaseConstant} case null [, default] case CasePattern {, CasePattern } [Guard] (旧来:case CasePattern [Guard]) default
Unnamed pattern (無名パターン)
2次元座標と色を持つ ColoredPoint
というレコードクラスがあった場合を考えます。
record Point(int x, int y) { } enum Color { RED, GREEN, BLUE } record ColoredPoint(Point p, Color c) { }
ColoredPoint
の x
座標だけがほしい場合、以下のようにパターンマッチで抽出することができます。
このような記述は、オブジェクトのネスト構造に応じて複雑になります。
if (r instanceof ColoredPoint(Point(int x, int y), Color c)) { System.out.println(x); }
無名パターンでは、レコードコンポーネントの型と名前を合わせて _
で宣言することができ、前述は以下のように簡略化できます。
if (r instanceof ColoredPoint(Point(int x, _), _)) { System.out.println(x); }
x
y
座標が必要な場合は以下のように書けます。
if (r instanceof ColoredPoint(Point(int x, int y), _)) { System.out.println("%d,%d", x, y); }
Color
コンポーネントのみを抽出する場合は以下のようになります。
if (r instanceof ColoredPoint(_, Color c)) { System.out.println(c); }
深くネストされたクラスの一部分を抽出するケースにおいて、無名パターンは可読性の大きな向上を提供します。