switchのパターン・マッチング(第4プレビュー)およびレコード・パターン(第2プレビュー)

Java®言語仕様バージョン20.0.1+9-29への変更

このドキュメントでは、switchパターン・マッチングレコード・パターン(どちらもJava SE 20のプレビュー機能)をサポートするためにJava言語仕様に加えられた変更について説明します。この機能の概要は、それぞれJEP 433およびJEP 432を参照してください。

変更は、JLSの既存のセクションについて説明しています。新しいテキストはこのように示され、削除されたテキストはこのように示されます。必要に応じて、説明と考察が端の方にグレーのボックスで囲まれて記載されています。

変更ログ:

2022-11-15:

2022-10-28: レコード・パターンの型引数の推論の詳細を追加しました。

2022-10-18: 初稿をリリースしました。様々なバグ修正に加えて、3番目のプレビュー仕様からの主な変更点は次のとおりです:

第3章: 字句構造

3.9 キーワード

ASCII文字で構成された51個の文字シーケンスは、キーワードとして使用するために予約されており、識別子(3.8)として使用することはできません。その他の同じくASCII文字で構成された16個の文字シーケンスは、出現するコンテキストによっては、キーワードや別のトークンとして解釈される可能性があります。

Keyword:
ReservedKeyword
ContextualKeyword
ReservedKeyword:
(次のいずれか)
abstract continue for new switch
assert default if package synchronized
boolean do goto private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const float native super while
_ (アンダースコア)
ContextualKeyword:
(次のいずれか)
exports permits to with
module provides transitive yield
non-sealed record uses
open requires var
opens sealed when

キーワードconstおよびgotoは、現在使用されていませんが、予約されています。これにより、これらのC++のキーワードがプログラムで誤って出現した場合にJavaコンパイラがより適切なエラー・メッセージを作成できるようになります。

キーワードstrictfpは廃止されています。新しいコードには使用しないでください。

キーワード_ (アンダースコア)は、将来パラメータ宣言で使用できるように予約されています。

trueおよびfalseはキーワードではなく、booleanリテラルです(3.10.3)。

nullはキーワードではなく、nullリテラルです(3.10.8)。

入力文字から入力要素への還元(3.5)の際に、コンテキスト・キーワードと概念的に一致する入力文字のシーケンスは、次の両方の条件が満たされる場合にのみ、コンテキスト・キーワードに還元されます。

  1. シーケンスは、次のように、構文文法(2.3)の適切なコンテキストで指定された終端として認識されます。

    • moduleおよびopenについては、ModuleDeclaration (7.7)で終端として認識される場合。

    • exportsopensprovidesrequirestousesおよびwithについては、ModuleDirectiveで終端として認識される場合。

    • transitiveについては、RequiresModifierで終端として認識される場合。

      たとえば、シーケンスrequires transitive ;の認識では、RequiresModifierが使用されていないため、このtransitiveという用語はコンテキスト・キーワードではなく識別子に還元されます。

    • varについては、LocalVariableType (14.4)またはLambdaParameterType (15.27.1)で終端として認識される場合。

      その他のコンテキストでは、識別子としてvarを使用しようとすると、varTypeIdentifier (3.8)ではないためにエラーが発生します。

    • yieldについては、YieldStatement (14.21)で終端として認識される場合。

      その他のコンテキストでは、識別子としてyieldを使用しようとすると、yieldTypeIdentifierUnqualifiedMethodIdentifierのどちらでもないためにエラーが発生します。

    • recordについては、RecordDeclaration (8.10)で終端として認識される場合。

    • non-sealedpermitsおよびsealedについては、NormalClassDeclaration (8.1)またはNormalInterfaceDeclaration (9.1)で終端として認識される場合。

    • whenについては、Guard (14.11.1)で終端として認識される場合。

  2. シーケンスの直前または直後に、JavaLetterOrDigitと一致する入力文字がない。

一般に、ソースコードで誤って空白を省略すると、「可能なかぎり長い変換」ルール(3.2)によって、入力文字のシーケンスが識別子としてトークン化されます。たとえば、12文字の入力文字シーケンスp u b l i c s t a t i cは、予約キーワードのpublicおよびstaticとしてではなく、常に識別子のpublicstaticとしてトークン化されます。2つのトークンを使用する場合は、それらを空白またはコメントで区切る必要があります。

前述のルールは、「可能なかぎり長い変換」ルールと連動して、コンテキスト・キーワードが現れるコンテキスト内で直感的な結果を生成します。たとえば、11文字の入力文字シーケンスv a r f i l e n a m eは、通常、識別子varfilenameとしてトークン化されますが、ローカル変数宣言では、最初の3文字の入力文字は、前述のルールの最初の条件によって暫定的にコンテキスト・キーワードvarとして認識されます。ただし、シーケンス内の空白の不足を見落とすと、その次の8文字の入力文字を識別子filenameとして認識することによる混乱が生じます。(これは、シーケンスが異なるコンテキストで異なるトークン化を経ることを意味します。ほとんどのコンテキストでは識別子ですが、ローカル変数宣言ではコンテキスト・キーワードと識別子です)。したがって、2番目の条件により、直後の入力文字のfJavaLetterOrDigitであるという理由で、コンテキスト・キーワードvarの認識を防止します。そのため、シーケンスv a r f i l e n a m eは、ローカル変数宣言の識別子varfilenameとしてトークン化されます。

コンテキスト・キーワードの慎重な認識の別の例として、15文字の入力文字のシーケンスn o n - s e a l e d c l a s sについて考えてみます。このシーケンスは、通常、識別子non、演算子-および識別子sealedclassの3つのトークンに変換されますが、最初の条件が成立する通常のクラス宣言では、最初の10の入力文字は暫定的にコンテキスト・キーワードnon-sealedとして認識されます。シーケンスを3つの非キーワード・トークンではなく2つのキーワード・トークン(non-sealedclass)に解釈されることを回避し、classの前の空白を省略するプログラマに報酬を与えないようにするために、2番目の条件によってコンテキスト・キーワードの認識を防止します。そのため、シーケンスn o n - s e a l e d c l a s sは、クラス宣言では3つのトークンとしてトークン化されます。

前述のルールでは、最初の条件は構文文法の詳細に依存しますが、Javaプログラミング言語のコンパイラは、入力プログラムを完全に解析することなくルールを実装できます。たとえば、コンテキスト・キーワードの有効な使用がキーワードとしてトークン化され、識別子の有効な使用が識別子としてトークン化されることがヒューリスティックで保証されているかぎり、ヒューリスティックを使用してトークナイザのコンテキスト状態を追跡できます。また、コンパイラでは常にコンテキスト・キーワードを識別子としてトークン化し、そうした識別子の特別な使用の認識は、それより後のフェーズに任せることもできます。

第5章: 変換およびコンテキスト

5.5 キャスト・コンテキスト

キャスト・コンテキストを使用すると、キャスト式(15.16)のオペランドは、キャスト演算子によって明示的に指定された型に変換されるようになります。割当てコンテキストおよび呼出しコンテキストとは対照的に、キャスト・コンテキストでは、5.1で定義されている変換をさらに多用できるとともに、これらの変換の組合せをより多く使用できます。

式がプリミティブ型の場合は、キャスト・コンテキストに次のいずれかを使用できます。

式が参照型の場合は、キャスト・コンテキストに次のいずれかを使用できます。

式にNULL型がある場合は、式は任意の参照型にキャストされることがあります。

キャスト・コンテキストで、チェックされているか部分的にチェックされていない縮小参照変換(5.1.6.25.1.6.3)が使用されると、式の値のクラスに対して実行時チェックが実行され、ClassCastExceptionが発生する可能性があります。それ以外の場合は、実行時チェックは実行されません。

チェックされていない縮小参照変換以外のキャスト変換によって式を参照型に変換できる場合、その式(またはその値)は、参照型とのダウンキャスト互換性があると表現されます。

参照型Sの式が、別の参照型Tとのダウンキャスト互換性がある場合、型Sは型Tダウンキャスト変換可能であると表現します。

次の表に、特定のキャスト・コンテキストで使用される変換を列挙します。それぞれの変換は、次の記号で示されます。

この表では、記号間のカンマは、キャスト・コンテキストがある変換とそれに続く別の変換を使用することを示します。型Objectは、8つのラッパー・クラスBooleanByteShortCharacterIntegerLongFloatDouble以外の任意の参照型を意味します。

表5.5-A.プリミティブ型へのキャスト

キャスト先
キャスト元
byte short char int long float double boolean
byte ω ωη ω ω ω ω -
short η η ω ω ω ω -
char η η ω ω ω ω -
int η η η ω ω ω -
long η η η η ω ω -
float η η η η η ω -
double η η η η η η -
boolean - - - - - - -
Byte ,ω - ,ω ,ω ,ω ,ω -
Short - - ,ω ,ω ,ω ,ω -
Character - - ,ω ,ω ,ω ,ω -
Integer - - - ,ω ,ω ,ω -
Long - - - - ,ω ,ω -
Float - - - - - ,ω -
Double - - - - - - -
Boolean - - - - - - -
Object , , , , , , , ,

表5.5-B.参照型へのキャスト

キャスト先
キャスト元
Byte Short Character Integer Long Float Double Boolean Object
byte - - - - - - - ,
short - - - - - - - ,
char - - - - - - - ,
int - - - - - - - ,
long - - - - - - - ,
float - - - - - - - ,
double - - - - - - - ,
boolean - - - - - - - ,
Byte - - - - - - -
Short - - - - - - -
Character - - - - - - -
Integer - - - - - - -
Long - - - - - - -
Float - - - - - - -
Double - - - - - - -
Boolean - - - - - - -
Object

例5.5-1.参照型のキャスト

class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
    int color;
    public void setColor(int color) { this.color = color; }
}
final class EndPoint extends Point {}

class Test {
    public static void main(String[] args) {
        Point p = new Point();
        ColoredPoint cp = new ColoredPoint();
        Colorable c;
        // The following may cause errors at run time because
        // we cannot be sure they will succeed; this possibility
        // is suggested by the casts:
        cp = (ColoredPoint)p;  // p might not reference an
                               // object which is a ColoredPoint
                               // or a subclass of ColoredPoint
        c = (Colorable)p;      // p might not be Colorable
        // The following are incorrect at compile time because
        // they can never succeed as explained in the text:
        Long l = (Long)p;            // compile-time error #1
        EndPoint e = new EndPoint();
        c = (Colorable)e;            // compile-time error #2
    }
}

ここで、最初のコンパイル時エラーが発生する理由は、クラス型のLongPointが関連していない(これらは同じではなく、どちらも他方のサブクラスではない)ためです。これらの間のキャストは常に失敗します。

2つ目のコンパイル時エラーは、EndPoint型の変数が、インタフェースColorableを実装する値を参照できないために発生します。これは、EndPointfinal型であり、final型の変数は常にコンパイル時の型と同じ実行時の型の値を保持するためです。そのため、変数eの実行時の型は正確にEndPoint型になっている必要があり、EndPoint型はColorableを実装しません。

例5.5-2.配列型のキャスト

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
    public String toString() { return "("+x+","+y+")"; }
}
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
    int color;
    ColoredPoint(int x, int y, int color) {
        super(x, y); setColor(color);
    }
    public void setColor(int color) { this.color = color; }
    public String toString() {
        return super.toString() + "@" + color;
    }
}

class Test {
    public static void main(String[] args) {
        Point[] pa = new ColoredPoint[4];
        pa[0] = new ColoredPoint(2, 2, 12);
        pa[1] = new ColoredPoint(4, 5, 24);
        ColoredPoint[] cpa = (ColoredPoint[])pa;
        System.out.print("cpa: {");
        for (int i = 0; i < cpa.length; i++)
            System.out.print((i == 0 ? " " : ", ") + cpa[i]);
        System.out.println(" }");
    }
}

このプログラムはエラーなしでコンパイルされ、次の出力を生成します。

cpa: { (2,2)@12, (4,5)@24, null, null }

例5.5-3.実行時の互換性のない型のキャスト

class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
    int color;
    public void setColor(int color) { this.color = color; }
}

class Test {
    public static void main(String[] args) {
        Point[] pa = new Point[100];

        // The following line will throw a ClassCastException:
        ColoredPoint[] cpa = (ColoredPoint[])pa;
        System.out.println(cpa[0]);
        int[] shortvec = new int[2];
        Object o = shortvec;

        // The following line will throw a ClassCastException:
        Colorable c = (Colorable)o;
        c.setColor(0);
    }
}

このプログラムはコンパイルにキャストを使用しますが、型に互換性がないため、実行時に例外をスローします。

第6章: 名前

6.3 宣言のスコープ

6.3.1 式のパターン変数のスコープ

6.3.1.6 switch

次のルール各ルールは、switchルールで構成されたswitchブロック(14.11.1)があるswitch(15.28)に適用されます。

次のルールは、switchラベルが付いた文グループで構成されたswitchブロック(14.11.1)があるswitch式に適用されます。

6.3.2 文のパターン変数のスコープ

6.3.2.5 for

基本のfor文(14.14.1)には、次のルールが適用されます:

拡張されたfor文(14.14.2)は基本のfor文の変換によって定義されるため、これに対して特別なルールを提供する必要はありません。

拡張されたforfor (p : e) Sの変換のプロパティ(pはレコード・パターン)は、パターンpによって導入されたパターン変数がS文で明確に照合されるということです。

6.3.2.6 switch

次のルール各ルールは、switchルールで構成されたswitchブロック(14.11.1)があるswitch(14.11)に適用されます。

次のルールは、switchラベルが付いた文グループで構成されたswitchブロック(14.11.1)があるswitch式に適用されます。

6.3.3 パターンのパターン変数のスコープ

6.3.3.1 レコード・パターン

次のルールは、レコード・パターンpに適用されます。

6.3.4 Switchラベルのパターン変数のスコープ

パターン変数は、パターン自体または関連するwhen式によって、caseパターンを持つcaseラベルによって導入でき、関連するswitch式(6.3.1.6)またはswitch文(6.3.2.6)の関連部分のスコープ内にあります。

caseラベルには、次のルールが適用されます:

第13章: バイナリ互換性

13.4 クラスの展開

13.4.2 sealednon-sealedおよびfinalクラス

13.4.2.1 sealedクラス

自由に拡張可能であったクラス(8.1.1.2)がsealedとして宣言されるように変更された場合、このクラスの既存のサブクラスのバイナリがロードされ、そのサブクラスがこのクラスの許容された直接サブクラス(8.1.6)でない場合には、IncompatibleClassChangeErrorがスローされます。広く配布されるクラスについては、このような変更はお薦めしません。

finalとして宣言されたクラスをsealedとして宣言されるよう変更しても、既存のバイナリとの互換性が失われることはありません。

sealedクラスの許容された直接サブクラスのセットにクラスを追加しても、既存のバイナリとの互換性が失われることはありません。

許容された直接サブクラスを追加してsealedクラスを展開することは、バイナリ互換性がある変更とみなされます。以前にエラーなしでリンクされていた既存のバイナリ(網羅的なswitch (14.11.1)を含むクラス・ファイルなど)は引き続きエラーなしでリンクされるためです。網羅的なswitchを含むクラス・ファイルは、それが切り替えたsealedクラスが新しい許容された直接サブクラスを持つように階層の所有者によって拡張されても、リンクに失敗することはありません。JVMは、網羅的なswitchを含むクラス・ファイルをリンクするときに網羅性チェックを実行する必要はありません。

網羅的なswitchの実行は、コンパイル時に認識されなかった許容されている直接サブクラスのインスタンスが検出された場合に、エラーで失敗する(MatchExceptionがスローされる)ことがあります(14.11.315.28.2)。厳密に言えば、このエラーは、sealedクラスのバイナリ互換性がない変更を示しているのではなく、より正確にはsealedクラスの移行互換性がない変更を示しています。

sealedクラスの許容された直接サブクラスのセットからクラスが削除された場合、削除されたクラスの既存のバイナリがロードされると、IncompatibleClassChangeErrorがスローされます。

sealed直接スーパークラスまたはsealed直接スーパーインタフェースを持たないクラスからsealed修飾子を削除しても、既存のバイナリとの互換性が失われることはありません。

sealed直接スーパークラスまたはsealed直接スーパーインタフェースを持つすべてのクラスはfinalsealedまたはnon-sealedである必要があるため、sealedクラスCsealed直接スーパークラスまたはsealed直接スーパーインタフェースを持っていた場合、sealed修飾子を削除すると、Cを再コンパイルできなくなります。

13.4.26 enumクラスの展開

enumクラス内でenum定数を追加したり、並べ替えても、既存のバイナリとの互換性が失われることはありません。

sealedクラス(13.4.2.1)と同様に、enumクラスにenum定数を追加することは、バイナリ互換性がある変更とみなされますが、コンパイル時に認識されていなかった新しいenum定数がswitchによって検出された場合、網羅的なswitch (14.11.1)の実行は失敗することがあります(14.11.315.28.2)。

enumクラスからenum定数を削除すると、そのenum定数に対応するpublicフィールド(8.9.3)が削除されます。この結果は、13.4.8に明示されています。広く配布されるenumクラスについては、このような変更はお薦めしません。

他のあらゆる点において、enumクラスに関するバイナリ互換性のルールは標準クラスに関するものと同じです。

13.5 インタフェースの展開

13.5.2 sealedおよびnon-sealedインタフェース

自由に拡張可能であったインタフェース(9.1.1.4)がsealedとして宣言されるように変更された場合、このインタフェースの既存のサブクラスまたはサブインタフェースのバイナリがロードされ、そのサブクラスまたはサブインタフェースがこのインタフェースの許容された直接サブクラスまたはサブインタフェース(9.1.4)でない場合には、IncompatibleClassChangeErrorがスローされます。広く配布されるクラスについては、このような変更はお薦めしません。

sealedインタフェースの許容された直接サブクラスまたはサブインタフェースのセットにそれぞれクラスまたはインタフェースを追加しても、既存のバイナリとの互換性が失われることはありません。

sealedクラス(13.4.2.1)と同様に、sealedインタフェースの許容された直接サブクラスまたはサブインタフェースを追加することは、バイナリ互換性がある変更とみなされますが、コンパイル時に認識されていなかった新しい許容された直接サブクラスまたはサブインタフェースのインスタンスがswitchによって検出された場合、網羅的なswitch (14.11.1)の実行はエラーで失敗する(MatchExceptionがスローされる)ことがあります(14.11.315.28.2)。

sealedインタフェースの許容された直接サブクラスまたはサブインタフェースのセットからクラスまたはインタフェースが削除された場合、削除されたクラスまたはインタフェースの既存のバイナリがロードされると、IncompatibleClassChangeErrorがスローされます。

sealedとして宣言されたインタフェースをnon-sealedとして宣言されるように変更しても、既存のバイナリとの互換性が失われることはありません。

non-sealedインタフェースIには、sealed直接スーパーインタフェースが必要です。sealed直接スーパーインタフェースを持つすべてのインタフェースはsealedまたはnon-sealedである必要があるため、non-sealed修飾子を削除すると、Iを再コンパイルできなくなります。

sealed直接スーパーインタフェースを持たないインタフェースからsealed修飾子を削除しても、既存のバイナリとの互換性が失われることはありません。

sealed直接スーパーインタフェースを持つすべてのインタフェースはsealedまたはnon-sealedである必要があるため、sealedインタフェースIsealed直接スーパーインタフェースを持っていた場合、sealed修飾子を削除すると、Iを再コンパイルできなくなります。

第14章: ブロックと文およびパターン

14.11 switch

switch文は、式の値に応じて、いくつかの文または式の1つに制御を転送します。

SwitchStatement:
switch ( Expression ) SwitchBlock

Expressionセレクタ式と呼ばれます。セレクタ式の型はcharbyteshortintCharacterByteShortIntegerStringまたはenum型(8.9)である必要があり、そうでない場合、コンパイル時にエラーが発生します。

セレクタ式の型のこれらの制限は、次のセクションに定義されている、セレクタ式と互換性があるというswitchブロックの概念に含まれるようになりました。

14.11.1 Switchブロック

switch文およびswitch式(15.28)の両方の本体は、switchブロックと呼ばれます。このサブセクションでは、switch文またはswitch式のどちらに出現するかに関係なく、すべてのswitchブロックに適用される一般的なルールを示します。他のサブセクションでは、switch文のswitchブロック(14.11.2)またはswitch式のswitchブロック(15.28.1)のいずれかに適用される追加ルールを示します。

SwitchBlock:
{ SwitchRule {SwitchRule} }
{ {SwitchBlockStatementGroup} {SwitchLabel :} }
SwitchRule:
SwitchLabel -> Expression ;
SwitchLabel -> Block
SwitchLabel -> ThrowStatement
SwitchBlockStatementGroup:
SwitchLabel : { SwitchLabel :} BlockStatements
SwitchLabel:
case CaseConstant {, CaseConstant}
case null [, default]
case CasePattern
default
CaseConstant:
ConditionalExpression
CasePattern:
Pattern [ Guard ]
Guard:
when Expression

switchブロックは次のいずれかで構成できます。

すべてのswitchルールおよびswitchラベルの付いた文グループは、switchラベル (caseラベルまたはdefaultラベルのいずれか)で始まります。複数のswitchラベルが、1つのswitchラベルが付いた文グループに許可されます。

caseラベルには、1つ以上のcase定数があります。すべてのcase定数は、定数式(15.29)またはenum定数の名前(8.9.1)のいずれかである必要があり、そうでない場合、コンパイル時にエラーが発生します。

Switchラベルおよびそのcase定数は、switchブロックに関連付けられていると言い表されます。switchブロックに関連付けられている2つのcase定数が同じ値を持つことはなく、そうでない場合、コンパイル時にエラーが発生します。

caseラベルは、case定数のリストまたは単一のcaseパターンのいずれかで構成されます。

すべてのcase定数は、(1) nullリテラル、(2)定数式(15.29)または(3) enum定数の名前(8.9.1)のいずれかである必要があり、そうでない場合、コンパイル時にエラーが発生します。単一のnullのcase定数をdefaultキーワードとペアにすることもできます。

caseパターンには、パターンに一致する値に対する追加のテストを表すオプションのwhen式を含めることができます。caseパターンは、(i)その要素にwhen式がない場合、または(ii)値がtrueの定数式であるwhen式がその要素にある(15.29)場合に、ガードなしと表現されます。それ以外の場合、ガード付きです。

switchラベルおよびそのcase定数およびcaseパターンは、switchブロックに関連付けられていると言い表されます。

特定のswitchブロックの場合、次の両方が当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します:

switchブロックに関連付けられたwhen式には、boolean型またはBoolean型が必要です。when式で使用されているが宣言されていない変数は、finalであるかeffectively finalであることが必要です(4.12.4)。when式がfalseの値を持つ定数式(15.29)の場合は、コンパイル時エラーになります。

次の両方に該当する場合、switch文またはswitch式のswitchブロックは、セレクタ式T型との互換性があります。

次のすべてに該当する場合、switch文またはswitch式のswitchブロックは、セレクタ式T型とのswitch互換性があります:

caseラベルは、booleanlongfloatまたはdouble型のセレクタ式をサポートしていません。switchブロックは、これらの型と連携するように設計されていません。

switch文またはswitch式のswitchブロックは、セレクタ式の型とのswitch互換性が必要であり、そうでない場合はコンパイル時にエラーが発生します。

switchラベルは、前者が後者に適用されるすべての値に適用される場合、別のswitchラベルより優先されると表現します。switchブロック内のswitchラベルが、それに続くswitchラベルより優先される場合は、コンパイル時にエラーが発生します。優先を決定するルールは次のとおりです。

switchラベルが付いた文グループで構成されるswitchブロックで、1つ以上のパターン変数を宣言するcaseパターンで文がラベル付けされていて、次のいずれかの場合、コンパイル時にエラーが発生します:

最初の条件により、文グループがパターン変数を初期化せずに別の文グループにフォール・スルーすることがなくなります。たとえば、前述の文グループから到達可能なcase Integer iというラベルが付けられた文では、パターン変数iは初期化されていません:

Object o = "Hello";
switch (o) {
    case String s:
        System.out.println("String: " + s );  // No break!
    case Integer i:
        System.out.println(i + 1);            // Error! Can be reached
                                              // without matching the
                                              // pattern `Integer i`
    default:
}

switchラベル文グループで構成されるswitchブロックでは、複数のラベルを文グループに適用できます。2番目の条件は、別のラベルのパターン変数を初期化せずに、あるラベルに基づいて文グループが実行されないようにします。たとえば:

Object o = "Hello World";
switch (o) {
    case String s:
    case Integer i:
        System.out.println(i + 1);  // Error! Can be reached
                                    // without matching the
                                    // pattern `Integer i`
    default:
}

Object obj = null;
switch (obj) {
    case null:
    case String s:
        System.out.println(s);      // Error! Can be reached
                                    // without matching the
                                    // pattern `String s`
    default:
}

これらの条件は両方とも、caseパターンがパターン変数を宣言している場合にのみ適用されます。一方、次の例は問題ありません:

record R() {}
record S() {}

Object o = "Hello World";
switch (o) {
    case String s:
        System.out.println(s);        // No break!
    case R():
        System.out.println("It's either an R or a string");
        break;
    default:
}

Object ob = new R();
switch (ob) {
    case R():
    case S():                         // Multiple case labels!
        System.out.println("Either R or an S");
        break;
    default:
}

Object obj = null;
switch (obj) {
    case null:
    case R():                         // Multiple case labels!
        System.out.println("Either null or an R");
        break;
    default:
}
14.11.1.1 網羅的なSwitchブロック

switch式またはswitch文のswitchブロックは、(i) switchブロックに関連付けられているdefaultラベルがある場合、または(ii) switchブロックに関連付けられたdefaultを持つcaseラベルがある場合、または(iii) switchブロックに関連付けられたすべてのcase定数およびガード付きでないcaseパターン(総称してcase要素)を含むセットが空ではなく、Tを網羅する場合、型Tのセレクタ式に対して網羅的です。次のように指定されます:

switch文またはswitch式は、そのswitchブロックがセレクタ式の型を網羅している場合、網羅的です。

14.11.1.2 実行可能なSwitchブロックおよび実行時に適用されるSwitchラベルの決定

一部のパターンの意味は照合対象の式の型によって決まるため、パターン・マッチング(14.30.2)の実行前にパターンを解決しておく必要があります。したがって、switch式またはswitch文のswitchブロックに関連付けられたcaseパターンも解決する必要があります。

実行可能なswitch式またはswitch文は、次のように、switchブロックに関連付けられたすべてのcaseパターンがセレクタ式Tの型に関して解決される文です:

switch文の実行(14.11.3)とswitch式の評価(15.28.2)の両方では、switchラベルがセレクタ式の値と一致するかどうかを判断する必要があります。switchブロック内のswitchラベルが特定の値と一致するかどうかを決定するために、値はswitchブロックに関連付けられたcase定数と比較されます。その後、次のように処理されます。

実行可能なswitch文(14.11.3)の実行と実行可能なswitch式(15.28.2)の評価の両方では、次のように、switchブロックに関連付けられたswitchラベルがセレクタ式の値に適用されるかどうかを判断する必要があります:

  1. 値がnull参照の場合、nullcase定数を含むcaseラベルが適用されます。

  2. 値がnull参照ではない場合、値に適用されるswitchブロックの最初の(ある場合) caseラベルを次のように決定します:

    • null以外のcase定数cを持つcaseラベルは、最初に値がボックス化解除変換(5.1.8)の対象になり、定数cがボックス化解除した値と等しくなる場合、CharacterByteShortまたはInteger型の値に適用されます。

      ボックス化解除変換は、ボックス化解除される値がNULL参照にならないことが保証されるため、正常に完了する必要があります。

      等価性は、==演算子で定義されます(15.21)。

    • null以外のcase定数cを持つcaseラベルは、定数cが値と等しい場合、CharacterByteShortまたはInteger型ではない値に適用されます。

      等価性は==演算子によって定義されますが、値がStringの場合はStringクラスのequalsメソッドによって定義されます。

    • caseパターンpを持つcaseラベルが値に適用されることを確認するには、最初に、値がパターンp (14.30.2)に一致するかどうかをチェックします。

      パターン・マッチングが突然完了した場合は、適用されるswitchラベルを判断するプロセスについても同じ理由で突然完了します。

      パターン・マッチングが成功し、caseパターンがガードされていない場合、このcaseラベルが適用されます。

      パターン・マッチングが成功し、caseパターンがガードされている場合、when式が評価されます。その結果がBoolean型の場合は、ボックス化解除変換(5.1.8)の対象になります。

      when式または後続のボックス化解除変換(ある場合)の評価がなんらかの理由で突然完了した場合、適用するswitchラベルを判断するプロセスは同じ理由で突然完了します。

      それ以外の場合は、その結果の値がtrueのときに、caseラベルが適用されます。

    • case null, defaultラベルはすべての値に適用されます

  3. 値がnull参照ではなく、ステップ2のルールに従ってcaseラベルが適用されない場合、defaultラベルがswitchブロックに関連付けられている場合は、そのラベルが適用されます。

単一のcaseラベルには、複数のcase定数を含めることができます。ラベルは、その定数のいずれかがセレクタ式の値と一致等しい場合、セレクタ式の値と一致しますに適用されます。たとえば、次のコードで、enum変数dayが表示されているenum定数のいずれかである場合、caseラベルは一致します:

switch (day) {
    ...
    case SATURDAY, SUNDAY :
        System.out.println("It's the weekend!");
        break;
    ...
}

パターンをサポートするswitchラベルが適用される場合、これは、パターンに対する値のパターン・マッチング・プロセスが成功したことによるものです(14.30.2)。値がパターンと正常に一致すると、パターン・マッチングのプロセスは、パターンによって宣言されたパターン変数を初期化します。

過去の理由から、defaultラベルは、defaultラベルの後に一部のラベルが出現する場合でも、すべてのcaseラベルが一致しなかった場合にのみ考慮されます。ただし、後続のラベルでは、null以外のcase定数(14.11.1)のみを使用でき、スタイルによっては、プログラマはdefaultラベルを最後に配置することをお薦めします。

nullは、定数式ではないためcase定数として使用できません。case nullが許可されている場合でも、そのcase内のコードを決して実行できないため、好ましくありません。つまり、セレクタ式が参照型(すなわち、Stringまたはボックス化プリミティブ型またはenum型)である場合、セレクタ式が実行時にnullに評価されると例外が発生します。Javaプログラミング言語の設計者の判断では、caseラベル一致がない、またはdefaultラベル一致がないより、例外を伝播するほうがよい結果になります。

CおよびC++で、switch文の本体は文(単数および複数)にすることができ、caseラベルをその文で直接含む必要はありません。単純なループを考えてみましょう。

for (i = 0; i < n; ++i) foo();

ここでnは正であると知られています。ダフスデバイスとして知られているトリックは、ループをアンロールするためにCまたはC++で使用できますが、これはJavaプログラミング言語では有効なコードではありません。

int q = (n+7)/8;
switch (n%8) {
    case 0: do { foo();    // Great C hack, Tom,
    case 7:      foo();    // but it's not valid here.
    case 6:      foo();
    case 5:      foo();
    case 4:      foo();
    case 3:      foo();
    case 2:      foo();
    case 1:      foo();
            } while (--q > 0);
}

幸いにも、このトリックは広く認識または使用されてはいないようです。さらに、今日ではあまり必要ではなくなりました。この種のコード変換は、適切に最新式の最適化コンパイラの領域にあります。

14.11.2 switch文のSwitchブロック

switchブロック(14.11.1)の一般的なルールに加えて、switch文のswitchブロックにはさらにルールがあります。

拡張されたswitch文は、(i)セレクタ式の型がcharbyteshortintCharacterByteShortIntegerStringまたはenum型でないか、(ii) switchブロックと関連付けられたcaseパターンまたはnullcase定数がある文です。

つまり、すべてのswitch文のswitchブロックに対して次のすべてが当てはまる必要があり、そうでない場合はコンパイル時にエラーが発生します。

Java SE 20より前のswitch文(およびswitch式)は、(i) セレクタ式の型が整数型(longを除く)、enum型またはStringに制限されていて、(ii) null以外のcase定数要素のみがサポートされていたという2つの制限がありました。さらに、switch式とは異なり、switch文は網羅的である必要はありませんでした。これは多くの場合、バグの検出を困難にする原因になり、switchラベルは適用されず、switch文は何もしません。たとえば:

enum E { A, B, C }

E e = ...;
switch (e) {
   case A -> System.out.println("A");
   case B -> System.out.println("B");
   // No case for C!
}

Java SE 20では、switch文は、caseパターンのサポートとともに前述の2つの制限も解除されているという点で拡張されています。Javaプログラミング言語の設計者が、拡張switch文はswitch式と揃える必要があり、網羅的である必要があると決めました。多くの場合、これは自明的なdefaultラベルの追加によって達成されます。たとえば、次の拡張switch文は網羅的ではありません。

Object o = ...;
switch (o) {    // Error - non-exhaustive switch!
    case String s -> System.out.println("A string!");
}

しかし、それは簡単に網羅的にすることができます。

Object o = ...;
switch (o) {
    case String s -> System.out.println("A string!");
    default -> {}
}

互換性の理由で、拡張されたswitch文ではないswitch文は網羅的である必要はありません。

14.11.3 switch文の実行

実行可能なswitch(14.11.1.2)は、セレクタ式の最初の評価によって実行されます。その後、次のように処理されます:セレクタ式の評価が突然完了する場合、switch文全体が同じ理由で突然完了します。

セレクタ式の評価が正常に完了する場合、結果が非nullで後続のボックス化解除変換(ある場合)が正常に完了するとき、switch文の実行は、解決されたswitchブロックに関連付けられたswitchラベルがセレクタ式の値と一致するに適用されるかどうかを決定することで続行されます(14.11.1.2)。その後、次のように処理されます。

switchブロックの文または式の実行が突然完了する場合、次のように処理されます。

例14.11.3-1.switch文のフォールスルー

セレクタ式がswitchラベルに一致しswitchラベルが適用され、そのswitchラベルがswitchルールに対するものである場合、switchラベルによって導入されたswitchルール式または文が実行されるのみです。文グループ用のswitchラベルの場合、そのswitchラベルに続くswitchブロックのすべてのブロック文が、後続のswitchラベルの後に出現するものを含めて実行されます。その効果は、CおよびC++でのように、文の実行が「ラベルをフォールスルー」できることです。

たとえば、次のプログラムの場合:

class TooMany {
    static void howMany(int k) {
        switch (k) {
            case 1: System.out.print("one ");
            case 2: System.out.print("too ");
            case 3: System.out.println("many");
        }
    }
    public static void main(String[] args) {
        howMany(3);
        howMany(2);
        howMany(1);
    }
}

caseのコードが次のcaseのコードにフォールスルーするswitchブロックを含みます。結果として、プログラムの出力は次のようになります。

many
too many
one too many

フォールスルーは判断しにくいバグの原因になる場合があります。コードがこのようにcaseからcaseにフォールスルーする予定ではない場合、break文を使用して、いつ制御を転送する必要があるかを示すことができます。または、switchルールを次のプログラムのように使用できます。

class TwoMany {
    static void howMany(int k) {
        switch (k) {
            case 1: System.out.println("one");
                    break;  // exit the switch
            case 2: System.out.println("two");
                    break;  // exit the switch
            case 3: System.out.println("many");
                    break;  // not needed, but good style
        }
    }
    static void howManyAgain(int k) {
        switch (k) {
            case 1 -> System.out.println("one");
            case 2 -> System.out.println("two");
            case 3 -> System.out.println("many");
        }
    }
    public static void main(String[] args) {
        howMany(1);
        howMany(2);
        howMany(3);
        howManyAgain(1);
        howManyAgain(2);
        howManyAgain(3);
    }
}

このプログラムは次を出力します。

one
two
many
one
two
many

14.14 for

14.14.2 拡張されたfor

拡張されたfor文の形式は次のようになります。

EnhancedForStatement:
for ( LocalVariableDeclaration EnhancedForDeclaration : )
EnhancedForStatementNoShortIf:
for ( LocalVariableDeclaration EnhancedForDeclaration : )
StatementNoShortIf
EnhancedForDeclaration:
LocalVariableDeclaration
RecordPattern

便宜上、ここでは4.38.38.4.1および14.4および14.30の次のプロダクションを示します:

LocalVariableDeclaration:
{VariableModifier} LocalVariableType VariableDeclaratorList
VariableModifier:
Annotation
final
LocalVariableType:
UnannType
var
VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclaratorId [= VariableInitializer]
VariableDeclaratorId:
Identifier [Dims]
Dims:
{Annotation} [ ] {{Annotation} [ ]}
RecordPattern:
ReferenceType ( [ PatternList ] )
PatternList:
Pattern { , Pattern }

の型は、配列型(10.1)またはRAW型Iterableのサブタイプである必要があります。そうしないと、コンパイル時にエラーが発生します。

拡張されたfor文のヘッダーEnhancedForDeclarationは、(i) VariableDeclaratorIdで指定された識別子の名前を持つローカル変数を宣言するローカル変数宣言または(ii)レコード・パターン(14.30.1)のいずれかです。拡張されたfor文が実行されると、次に、ローカル変数がループの反復ごとに、Iterableの連続する要素、または式によって生成された配列に初期化されるか、ループの反復ごとに、Iterableの連続する要素または式によって生成された配列は、レコード・パターンと照合されます

反復型は次のように定義されます:

拡張されたfor文のヘッダーで宣言されたローカル変数のルールは、14.4で規定され、LocalVariableTypevarの場合に適用されるそのセクション内のルールが無視されます。

さらに、次のすべてが当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します。

拡張されたfor文のEnhancedForDeclarationLocalVariableDeclarationの場合、次の両方がtrueであるか、コンパイル時にエラーが発生します。

14.4で指定されたコンパイル時のルールのために、拡張されたfor文のEnhancedForDeclarationLocalVariableDeclarationの場合、型がfor文の反復型であるイニシャライザがあるかのように処理されます。

これらの変更により、ローカル変数宣言(特にvarの処理)の詳細な処理が14.4に移行されます。ローカル変数宣言を使用した拡張されたforの解釈方法には、大幅な変更はありません。

拡張されたfor文のEnhancedForDeclarationがレコード・パターンである場合、次の両方がtrueであるか、コンパイル時にエラーが発生します:

拡張されたfor文のヘッダーEnhancedForDeclarationで宣言されたすべてのローカル変数のスコープとシャドウイングは、6.3および6.4で規定されています。

ネストされたクラスまたはインタフェースまたはラムダ式からのこのようなローカル変数への参照は、6.5.6.1で規定されているように制限されます。

拡張されたfor文のヘッダーで宣言されたローカル変数の型Tは、次のように決定されます:

これらのルールは、「反復型」の定義と、ローカル変数宣言が反復型のイニシャライザがあるかのように処理されるというアサーションによって組み込まれます。

拡張されたfor文の明確な意味は、次のように基本のfor文への変換によって与えられます。

たとえば、次のコードがあるとします。

List<? extends Integer> l = ...
for (float i : l) ...

次のように変換されます。

for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) {
    float #i0 = (Integer)#i.next();
    ...
for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) {
    float #i0 = #i.next();
    ...
}

例14.14-1.拡張されたforおよび配列

整数配列の合計を計算する次のプログラムは、拡張されたforが配列に対してどのように機能するかを示しています。

int sum(int[] a) {
    int sum = 0;
    for (int i : a) sum += i;
    return sum;
}

例14.14-2.拡張されたforおよびボックス化解除変換

次のプログラムは、拡張されたfor文と自動ボックス化解除を組み合せて、ヒストグラムを度数分布表に変換します。

Map<String, Integer> histogram = ...;
double total = 0;
for (int i : histogram.values())
    total += i;
for (Map.Entry<String, Integer> e : histogram.entrySet())
    System.out.println(e.getKey() + " " + e.getValue() / total);
}

14.30 パターン

パターンは、値に対して実行できるテストを示します。パターンは文および式のオペランドとして出現し、テストする値を提供します。パターンでは、パターン変数と呼ばれるローカル変数を宣言できます

パターンに対して値をテストするプロセスはパターン・マッチングと呼ばれます。値がパターンに正常に一致する場合、パターン・マッチング・プロセスが、そのパターンによって宣言されたパターン変数変数(存在する場合)を初期化します。

パターン変数は、パターン・マッチングが成功するスコープ(6.3)内にのみあるため、パターン変数が初期化されています。初期化されていないパターン変数を使用することはできません。

14.30.1 パターンの種類

型パターンは、値がパターンに出現する型のインスタンスであるかどうかをテストするために使用されます。レコード・パターンは、値がレコード・クラス型のインスタンスであるかどうかをテストし、そうである場合は、レコード・コンポーネント値に対してパターン・マッチングを再帰的に実行するために使用されます。パターンは、カッコで囲むと読みやすくなります。

Pattern:
TypePattern
RecordPattern
ParenthesizedPattern
TypePattern:
LocalVariableDeclaration
RecordPattern:
ReferenceType ( [ PatternList ] )
PatternList :
Pattern { , Pattern }
ParenthesizedPattern:
( Pattern )

便宜上、ここでは4.38.38.4.1、および14.4の次のプロダクションを示します。

LocalVariableDeclaration:
{VariableModifier} LocalVariableType VariableDeclaratorList
VariableModifier:
Annotation
final
LocalVariableType:
UnannType
var
VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclaratorId [= VariableInitializer]
VariableDeclaratorId:
Identifier [Dims]
Dims:
{Annotation} [ ] {{Annotation} [ ]}

UnannTypeについては、8.3を参照してください。

型パターンは、パターン変数として知られる1つのローカル変数を宣言します。ローカル変数宣言の識別子は、パターン変数の名前を指定します。

レコード・パターンのネストされたパターン・リストに要素として出現しない型パターンは、トップレベル型パターンと呼ばれています。

型パターンで宣言されたローカル変数のルールについては、14.4に規定されています。さらに、次のすべてが当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します。

トップレベル型パターンで宣言されたパターン変数の型は、LocalVariableTypeによって示される参照型です。

トップレベル型パターンではない型パターンで宣言されたパターン変数の型は、次のように決定されます:

型パターンの型は、そのパターン変数の型です。

レコード・パターンは、ReferenceTypeとネストされたパターン・リストで構成されます。ReferenceTypeがレコード・クラス・タイプ(8.10)でない場合、コンパイル時エラーが発生します。

ReferenceTypeがRAW型である場合、18.5.5で説明されているように、レコード・パターンの型が推論されます。レコード・パターンに対して型を推論できない場合、コンパイル時にエラーが発生します。

それ以外の場合、レコード・パターンの型はReferenceTypeです。

レコード・パターンのネストされたパターン・リストの長さは、ReferenceTypeで指定されたレコード・クラスの宣言内のレコード・コンポーネント・リストと同じ長さである必要があります。そうでない場合は、コンパイル時にエラーが発生します。

現時点では、可変個引数レコード・パターンはサポートされていません。これは、Javaプログラミング言語の将来のバージョンでサポートされる可能性があります。

レコード・パターンでは、ネストされたパターン・リスト内のパターンで宣言されているローカル変数(ある場合)を宣言します。

カッコ付きのパターンは、含まれたパターンによって宣言されたローカル変数を宣言します。

特殊なanyパターンもあります。これは、パターンの解決のプロセスから発生するパターンです(14.30.2)。

現時点では、anyパターンの構文は存在しないため、パターンinstanceof式のパターンとして使用することも、switch式またはswitch文のswitchラベルのパターンとして使用することもできません。Javaプログラミング言語の将来のバージョンで、この制限が緩和される可能性があります。

anyパターンは、パターン変数として知られる1つのローカル変数を宣言します。

anyパターンのパターン変数は、参照型の型を持ちます。

eは、eTとダウンキャスト互換性がある場合、型Tのパターンと互換性があります(5.5)。

パターンとの式の互換性は、instanceofパターン一致演算子(15.20.2)で使用されます。

14.30.2 パターン・マッチング

パターン・マッチングは、実行時にパターンに対して値をテストするプロセスです。パターン・マッチングは、文実行(14.1)および式評価(15.1)とは異なります。値がパターンに正常に一致する場合、パターン・マッチング・プロセスが、パターンによって宣言されたすべてのパターン変数(ある場合)を初期化します。

パターン・マッチングの実行前に、すべてのパターンが、まず、照合される式のタイプ(switch文のセレクタ式またはswitch文、あるいはinstanceof式のRelationalExpressionのいずれか)に関して解決されます。その結果として、パターンが修正される可能性があります。

Uでパターンを解決するには次のように指定します。

このパターンを解決するプロセスは、レコード・パターンの正しいセマンティクスを取得することです。次の例について考えてみます。

class Super {}
class Sub extends Super {}
record R(Super s) {}

Rのすべてのnull以外の値は、式new R(null)の評価結果の値を含め、パターンR(Super s)に一致すると期待します。(null値がパターンSuper sと一致しないとしても)。ただし、レコード・コンポーネントのnull値がパターンSub sと一致しないため、この値がパターンR(Sub s)と一致することは期待しません

次に、ネストされたパターン・リスト内で出現するパターンの意味は、レコード宣言に関して決定されます。解決では、nullを含むすべての値と一致する必要があるネストされたパターン・リストに現れるany型のパターンを特別なanyパターンのインスタンスに置き換えます。前述の例では、パターンR(Sub s)はパターンR(Sub s)に解決されますが、パターンR(Super s)は型Rのレコード・パターンと、anyパターンを含むネストされたパターン・リストに解決されます。

パターン・マッチングのプロセスには、式の評価または文の実行が関与する場合があります。したがって、式の評価または文の実行が突然完了する場合は、パターン・マッチングが突然完了すると表現されます。突然の完了には常に理由が関連付けられており、それは常に、指定された値を持つthrowです。パターン・マッチングは、突然完了しない場合、正常に完了すると言い表されます。

値が解決されたパターンと一致するかどうかを決定するルールおよびパターン変数を初期化するルールは、次のとおりです:

null参照である値を対象とするルールはありません。これは、パターン・マッチングを実行する単独のコンストラクトであるinstanceofパターン一致演算子(15.20.2)が、値がnull参照ではない場合にのみパターン・マッチングを実行するためです。Javaプログラミング言語の将来のバージョンで、他の式および文でのパターン・マッチングが許可される可能性があります。

14.30.3 パターンのプロパティ

次のいずれかの規則が適用される場合、パターンpは型T適用可能であると表現されます。

パターンpは、型Tのすべての値がp (型Tpが解決(14.30.2)された後)に一致する場合、型T無条件であると表現されます。これは、次のように定義されます。

null参照はanyレコード・パターンと一致しないため、レコード・パターンはanyタイプで無条件ではありません。

qと一致するすべての値がpとも一致する場合、パターンpは別のパターンqよりも優先されると表現され、次のように定義されます。

第15章: 式

15.20 関係演算子

15.20.2 instanceof演算子

instanceof式は型比較またはパターン・マッチングのいずれかを実行できます。

InstanceofExpression:
RelationalExpression instanceof ReferenceType
RelationalExpression instanceof Pattern

instanceofキーワードの右側のオペランドがReferenceTypeである場合、instanceofキーワードは型比較演算子です。

instanceofキーワードの右側のオペランドがPatternである場合、instanceofキーワードはパターン一致演算子です。

次のルールは、instanceofが型比較演算子の場合に適用されます。

次のルールは、instanceofがパターン一致演算子の場合に適用されます。

例15.20.2-1.型比較演算子

class Point   { int x, y; }
class Element { int atomicNumber; }
class Test {
    public static void main(String[] args) {
        Point   p = new Point();
        Element e = new Element();
        if (e instanceof Point) {  // compile-time error
            System.out.println("I get your point!");
            p = (Point)e;  // compile-time error
        }
    }
}

このプログラムの結果、コンパイル時に2つのエラーが発生します。Elementまたは使用可能なそのサブクラス(ここには示されていません)のインスタンスがPointのサブクラスのインスタンスではない可能性があるため、キャスト(Point)eは正しくありません。まったく同じ理由により、instanceof式も正しくありません。一方、クラスPointElementのサブクラスであった場合(この例では明らかに奇妙な表記です):

class Point extends Element { int x, y; }

キャストは可能になりますが、実行時のチェックが必要になるため、instanceof式が実用的かつ有効です。キャスト(Point)eは、eの値を型Pointに正確にキャストできない場合には実行されないため、これによって例外が呼び出されることはありません。

Java SE 16より前は、reifiableにするには型比較演算子のReferenceTypeオペランドが必要でした(4.7)。これにより、そのすべての型引数がワイルドカードでないかぎり、パラメータ化型を使用できませんでした。多くのパラメータ化型の使用を許可するために、その要件はJava SE 16で取り除かれました。たとえば、次のプログラムで、静的型List<Integer>のあるメソッド・パラメータxに、さらに詳細なパラメータ化型ArrayList<Integer>があるかどうかを実行時にテストすることは有効です。

import java.util.ArrayList;
import java.util.List;

class Test2 {
    public static void main(String[] args) {
        List<Integer> x = new ArrayList<Integer>();

        if (x instanceof ArrayList<Integer>) {  // OK
            System.out.println("ArrayList of Integers");
        }
        if (x instanceof ArrayList<String>) {  // error
            System.out.println("ArrayList of Strings");
        }
        if (x instanceof ArrayList<Object>) {  // error
            System.out.println("ArrayList of Objects");
        }
    }
}

最初のinstanceof式は、List<Integer>からArrayList<Integer>へのキャスト変換があるため有効です。ただし、2番目および3番目のinstanceof式では、List<Integer>からArrayList<String>またはArrayList<Object>へのキャスティング変換がないため、どちらもコンパイル時にエラーが発生します。

15.28 switch

switch式は、式の値に応じて、いくつかの文または式の1つに制御を転送します。その式のすべての可能な値が処理される必要があり、いくつかの文または式のすべてがswitch式の結果の値を生成する必要があります。

SwitchExpression:
switch ( Expression ) SwitchBlock

Expressionセレクタ式と呼ばれます。セレクタ式の型はcharbyteshortintCharacterByteShortIntegerStringまたはenum型(8.9)である必要があり、そうでない場合、コンパイル時にエラーが発生します。

switch式およびswitch文(14.11)の両方の本体は、switchブロックと呼ばれます。switch式またはswitch文のどちらに出現するかに関係なく、すべてのswitchブロックに適用される一般的なルールは、14.11.1に示されています。便宜上、ここでは14.11.1の次のプロダクションを示します。

SwitchBlock:
{ SwitchRule {SwitchRule} }
{ {SwitchBlockStatementGroup} {SwitchLabel :} }
SwitchRule:
SwitchLabel -> Expression ;
SwitchLabel -> Block
SwitchLabel -> ThrowStatement
SwitchBlockStatementGroup:
SwitchLabel : { SwitchLabel :} BlockStatements
SwitchLabel:
case CaseConstant {, CaseConstant}
default
SwitchLabel:
case CaseConstant {, CaseConstant }
case CasePattern
case null [, default]
default
CaseConstant:
ConditionalExpression
CasePattern:
Pattern [ Guard ]
Guard:
when Expression

15.28.1 switch式のSwitchブロック

switchブロック(14.11.1)の一般的なルールに加えて、switch式のswitchブロックにはさらにルールがあります。

つまり、switch式のswitchブロックに対して次のすべてが当てはまる必要があり、そうでない場合はコンパイル時にエラーが発生します。

switch式のswitchブロックがswitchルールで構成されている場合、コンパイル時にエラーが発生します。すべてのswitchルール・ブロックは完了できません。ただし、1つ以上のswitchルール・ブロックは正常に完了できます(14.22)。

switchのswitchブロックがswitchラベル付き文グループで構成されている場合、コンパイル時にエラーが発生します。次に、ただし、switchブロックの最後の文は正常に完了できません。できます。およびまたはswitchブロックは、最後のswitchラベル付き文グループの後にラベルがありません1つ以上のswitchラベルがあります

switch式が網羅的でない場合(14.11.1.1)、コンパイル時にエラーが発生します。

switch式は、switch文とは異なり、空のswitchブロックを持つことができません。

これは、次の結果式の要件で説明されています。

さらに、switch式は、switchブロックの矢印(->)の右にどの式が出現するか、つまり、どの式をswitchルール式として使用できるかという観点で、switch文と異なります。switch式では、任意の式をswitchルール式として使用できますが、switch文では、文の式のみを使用できます(14.11.1)。

switch式の結果式は、次のように決定されます。

switch式に結果式がない場合、コンパイル時にエラーになります。

switch式が割当てコンテキストまたは呼出しコンテキスト(5.25.3)に出現する場合、それは複合式です。それ以外の場合はスタンドアロン式です。

複合switch式がターゲット型Tの特定の種類のコンテキストに出現する場合、その結果式も同様にターゲット型Tの同じ種類のコンテキストに出現します。

複合switch式は、その結果式のそれぞれがTと互換性がある場合、ターゲット型Tと互換性があります。

複合switch式の型は、そのターゲット型と同じです。

スタンドアロンswitch式の型は、次のように決定されます。

15.28.2 switch式の実行時評価

実行可能switch(14.11.1.2)は、セレクタ式の最初の評価によって評価されます。その後、次のように処理されます:セレクタ式の評価が突然完了する場合、switch式の評価が同じ理由で突然完了します。

セレクタ式の評価が正常に完了する場合、結果が非nullで後続のボックス化解除変換(ある場合)が正常に完了するとき、switch式の評価は、解決されたswitchブロックに関連付けられたswitchラベルがセレクタ式の値と一致するに適用されるかどうかを決定することで続行されます(14.11.1.2)。その後、次のように処理されます。

switchブロックの文または式の実行が突然完了する場合、次のように処理されます。

第16章: 明確な割当て

16.2 明確な割当ておよび文

16.2.9 switch

第18章: 型推論

18.5 推論の使用

18.5.5 レコード・パターンの型推論

汎用レコード・クラスRのレコード・パターン(14.30.1)が、型Tの値がそれと照合され、パターンがRの型引数を提供しないコンテキストで出現する場合、次に説明するように型引数が推論されます。

  1. TがRAW型Rへのダウンキャスト変換可能(5.5)でない場合、推論は失敗します。

  2. それ以外の場合、P1, ..., Pn (n 1)はRの型パラメータで、α1, ..., αnは推論変数にします。初期範囲セットB0は、18.1.3で説明されているように、P1, ..., Pnの宣言された範囲から生成されます。

  3. T'は、次のようにTから導出されます:

    • Tがワイルドカードでパラメータ化された型である場合、β1, ..., βk (k 1)を推論変数にします。ここで、kT内のワイルドカード型引数の数です。T'は、Tの各ワイルドカード型引数をβi (1≤ ik)に置き換えた結果です。

      β1, ..., βkの追加範囲は、次のように、範囲セットB1を形成するためにB0に組み込まれています:

      • βi (1≤ ik)がTのワイルドカードを上限Uに置き換えた場合、範囲βi <: Uが範囲セットに出現します

      • βi (1≤ ik)がTのワイルドカードを下限Lに置き換えた場合、範囲L <: βiが範囲セットに出現します

      • Q1, ..., Qm (m 1)をT'で指定されたクラスまたはインタフェースの型パラメータにし、A1, ..., AmT'の型引数にします。

        βi (1 ≤ ik)ごとに、およびβi (1 ≤ im)に対応する型パラメータのTypeBoundの型U (&で区切られている)ごとに、範囲βi <: U[Q1:=A1, ..., Qm:=Am]が範囲セットに存在します。βiに対応する型パラメータのTypeBoundがない場合、またはTypeBound (依存関係のみ)から適切な上限が導出されない場合、範囲βi <: Objectはセットに存在します。

    • Tが他のクラスまたはインタフェース型である場合、T'Tと同じであり、B1B0と同じです。

    • Tが型変数または交差型の場合は、交差型の型変数または要素の上限ごとに、このステップとステップ4が再帰的に繰り返されます。ステップ3と4で作成したすべての範囲は、単一の範囲セットに組み込まれています。

  4. T'が汎用クラスGのパラメータ化であり、Gのパラメータ化でもあるR<α1, ..., αn>のスーパータイプが存在する場合、R'をそのスーパータイプにします。制約式‹T' = R'›が削減され(18.2)、結果として得られた範囲がB1に組み込まれ、新しい範囲セットB2が生成されます。

    それ以外の場合、B2B1と同じです。

    B2に範囲falseが含まれている場合、推論は失敗します。

  5. それ以外の場合、推論変数α1, ..., αnB2 (18.4)で解決されます。通常の解決とは異なり、このケースの解決では、推論変数のインスタンス化を適切な下限または適切な上限から生成しようとするステップがスキップされます。かわりに、新しいインスタンス化が、新しい型変数を導入するステップに直接スキップして作成されます。

    解決に失敗すると、推論は失敗します。

  6. それ以外の場合、A1, ..., Anα1, ..., αnの解決されたインスタンス化にし、Y1, ..., Yp (p ≥ 0)を解決で導入された新しい型変数にします。

    レコード・パターンの型は、Y1, ..., Yp (4.10.5)に関するR<A1, ..., An>の上方投影です。

例18.5.5-1.レコード・パターンの型推論

次のプログラムは、レコード・クラスのパラメータ化を推論します:

record Mapper<T>(T in, T out) implements UnaryOperator<T> {
    public T apply(T arg) { return in.equals(arg) ? out : null; }
}

void test(UnaryOperator<? extends CharSequence> op) {
    if (op instanceof Mapper(var in, var out)) {
        boolean shorter = out.length() < in.length();
    }
}

この場合、Rはレコード・クラスMapperであり、Tは型UnaryOperator<? extends CharSequence>です。TはRAWのMapperにダウンキャスト変換可能であるため、Mapper<α>内のαのインスタンス化を推論します。 T'UnaryOperator<β>型であり、βは上限CharSequenceです。

Mapper<α>にはスーパータイプUnaryOperator<α>があるため、制約式UnaryOperator<β> = UnaryOperator<α>を減らします。これにより、範囲はα = βになります。組み込むと、さらにα <: CharSequenceを推論します。

ここで、αを解決し、上限がCharSequenceの新しい型変数であるα = Yを生成します。 最後に、Yに関するMapper<Y>の上方投影を見つけ、レコード・パターンの型がMapper<? extends CharSequence>であると推論します。

レコード・パターンの型がわかると、ネストされたパターンと照合されるコンポーネント型を確認できます。パターン変数inおよびoutは両方ともCharSequence型を持ちます。