パターン、instanceof、switchのプリミティブ型(第2プレビュー)

Java®言語仕様の変更点・バージョン24+36-3646

このドキュメントでは、Java SE 24のプレビュー機能であるinstanceofのパターン・マッチングに対するプリミティブ型をサポートするためのJava言語仕様の変更について説明します。この機能の概要は、JEP:488を参照してください。

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

変更ログ:

2024-10-29: 細かい修正。

2024-10-14: instanceofが推論される型をRelationalExpressionの型として使用することが、15.20.2で明確にされます。

第1章: 概要

1.4 事前定義済のクラスとインタフェースの関係

前述のように、この仕様は、Java SE Platform APIのクラスとインタフェースを指すことがよくあります。特に、一部のクラスとインタフェースは、Javaプログラミング言語と特別な関係があります。例として、ObjectClassClassLoaderStringおよびThreadなどのクラス、クラスjava.math.BigDecimal、インタフェースjava.io.Serializableおよびパッケージjava.lang.reflectのクラスとインタフェースなどが含まれます。この仕様は、そのようなクラスとインタフェースの動作を制約しますが、それらの完全な仕様は提示しません。読者は、Java SE Platform APIのドキュメントを参照してください。

したがって、この仕様にはリフレクションの詳細な説明はありません。多くの言語構造は、コア・リフレクションAPI (java.lang.reflect)と言語モデルAPI (javax.lang.model)に類似したものがありますが、通常、ここでは説明しません。たとえば、オブジェクトを作成する方法をリストする場合、通常、これをコア・リフレクションAPIで実現する方法は含めていません。読者は、テキストに記載されていなくても、それらの追加のメカニズムに注意する必要があります。

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

Javaプログラミング言語で記述されるすべての式は、結果を生成しないか(15.1)、コンパイル時に推論できる型(15.3)のいずれかになります。ほとんどのコンテキストに現れる式は、そのコンテキストで想定される型との互換性がある必要があります。この型はターゲット型と呼ばれます。便宜上、式と式の周囲のコンテキストとの互換性は、次の2つの方法で容易になります:

どちらの方法でも適切な型を生成できない場合は、コンパイル時にエラーが発生します。

式が多相的な式かどうかを決定するルールと、多相的な式の場合の特定のコンテキストでの型と互換性は、コンテキストの種類と式の形式によって異なります。ターゲット型は、式の型に影響を与えることに加えて、適切な型の値を生成するために式の実行時の動作に影響を与えることがあります。

同様に、ターゲット型で暗黙的な変換を許可するかどうかを決定するルールは、コンテキストの種類、式の型、および1つの特殊なケースでは定数式の値(15.29)によって異なります。型Sから型Tへの変換により、コンパイル時に型Sの式を型Tであるかのように処理できます。場合によっては、変換の妥当性をチェックするためや、式の実行時の値を新しい型Tに適した形式に変換するために、実行時に対応する処置が必要になることがあります。

例5.0-1.コンパイル時および実行時の変換

Javaプログラミング言語で可能な変換は、いくつかの広範なカテゴリに分類されます:

多相的な式がコンテキストの影響を受ける可能性がある、または暗黙的な変換が発生する可能性のある変換コンテキストには7つの種類があります。コンテキストの種類ごとに、多相的な式の型付けのルールが異なり、前述の一部のカテゴリでは変換できますが、別のカテゴリでは変換できません。コンテキストは次のとおりです:

「変換」という用語は、特定のコンテキストで許可される変換を具体的にせずに記述するためにも使用されます。たとえば、ローカル変数のイニシャライザである式が「割当て変換」の対象であると表現した場合は、割当てコンテキストのルールに従って、特定の変換がその式に暗黙的に選択されることを意味します。別の例として、式が「キャスト変換」されると表現した場合は、式の型がキャスト・コンテキストで許可されたとおりに変換されることを意味します。

例5.0-2.様々なコンテキストでの変換

class Test {
    public static void main(String[] args) {
        // Casting conversion (5.5) of a float literal to
        // type int. Without the cast operator, this would
        // be a compile-time error, because this is a
        // narrowing conversion (5.1.3):
        int i = (int)12.5f;

        // String conversion (5.4) of i's int value:
        System.out.println("(int)12.5f==" + i);

        // Assignment conversion (5.2) of i's value to type
        // float. This is a widening conversion (5.1.2):
        float f = i;

        // String conversion of f's float value:
        System.out.println("after float widening: " + f);

        // Numeric promotion (5.6) of i's value to type
        // float. This is a binary numeric promotion.
        // After promotion, the operation is float*float:
        System.out.print(f);
        f = f * i;

        // Two string conversions of i and f:
        System.out.println("*" + i + "==" + f);

        // Invocation conversion (5.3) of f's value
        // to type double, needed because the method Math.sin
        // accepts only a double argument:
        double d = Math.sin(f);

        // Two string conversions of f and d:
        System.out.println("Math.sin(" + f + ")==" + d);
    }
}

このプログラムでは、次の出力が生成されます。

(int)12.5f==12
after float widening: 12.0
12.0*12==144.0
Math.sin(144.0)==-0.49102159389846934

5.1 変換の種類

5.1.2 プリミティブの拡大変換

プリミティブ型に対する19の特定の変換は、プリミティブの拡大変換と呼ばれます:

数値の全体的な絶対値に関する情報が失われない拡大プリミティブ変換数値が正確に保たれる次の場合正確な拡大プリミティブ変換と呼ばれ、数値は正確に保持されます。このような変換は、次のいずれかになります:

intからfloatlongからfloatまたはlongからdoubleへの拡大プリミティブ変換は、制度が失われることがあります。つまり、値の右端ビットの一部が失われる結果を招く可能性があります。この場合、結果の浮動小数点値は、最近接丸めポリシー(15.4)を使用して適切に丸められたバージョンの整数値になります。

符号付き整数値から整数型Tへの拡大変換は、より幅が広い形式を満たすために単に整数値の2の補数表現を符号拡張します。

charから整数型Tへの拡大変換は、より幅が広い形式を満たすためにchar値の表現をゼロ拡張します。

intからfloatlongからfloatintからdoubleまたはlongからdoubleへの拡大変換は、整数形式からバイナリ浮動小数点形式への変換に関するIEEE 754のルールに従います。

floatからdoubleへの拡大変換は、バイナリ浮動小数点形式間の変換に関するIEEE 754のルールに従います。

精度の損失が発生する可能性はありますが、プリミティブの拡大変換によって実行時の例外が発生することはありません(11.1.1)。

例5.1.2-1.プリミティブの拡大変換

class Test {
    public static void main(String[] args) {
        int big = 1234567890;
        float approx = big;
        System.out.println(big - (int)approx);
    }
}

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

-46

これは、型floatの値の有効桁数が9桁でないため、型intから型floatへの変換中に情報が失われたことを示しています。

5.1.3 プリミティブの縮小変換

プリミティブ型に対する22の特定の変換は、プリミティブの縮小変換と呼ばれます:

プリミティブの縮小変換では、数値の全体的な絶対値に関する情報が失われ、精度と範囲も失われる可能性があります。

符号付き整数を整数型Tに縮小変換すると、右端のn個のビットを除くすべてのビットが単に破棄されます。このnは、型Tを表すために使用されるビット数です。数値の絶対値に関する情報が失われる可能性に加えて、結果の値の符号が入力値の符号と異なることもあります。

charを整数型Tに縮小変換すると、同様に右端のn個のビットを除くすべてのビットが単に破棄されます。このnは、型Tを表すために使用されるビット数です。数値の絶対値に関する情報が失われる可能性に加えて、charが16ビットの符号なし整数値を表す場合でも、結果の値は負の数値になることがあります。

浮動小数点数を整数型Tに縮小変換するには、次の2つのステップを実行します:

  1. 最初のステップでは、浮動小数点数がlong (Tlongの場合)またはint (Tbyteshortcharまたはintの場合)に変換されます。

    • 浮動小数点数がNaN (4.2.3)の場合、変換の最初のステップの結果は、intまたはlong 0になります。

    • それ以外の場合、浮動小数点数が無限大でない場合、浮動小数点値はゼロ丸めポリシー(4.2.4)に従って整数値Vに丸められます。その次には、2つのケースがあります:

      1. Tlongのときに、この整数値がlongとして表すことができる場合、最初のステップの結果はlongVになります。

      2. それ以外の場合、この整数値がintとして表すことができる場合、最初のステップの結果はintVになります。

    • それ以外の場合は、次の2つのケースのいずれかに当てはまる必要があります:

      1. 値は小さすぎるもの(大きな負の値または負の無限大)であることが必要で、最初のステップの結果は、型intまたは型longで表現可能な最小値になります。

      2. 値は大きすぎる(大きな正の値または正の無限大)ものであることが必要で、最初のステップの結果は、型intまたは型longで表現可能な最大値になります。

  2. 2番目のステップ:

    • Tintまたはlongの場合、変換の結果は最初のステップの結果になります。

    • Tbytecharまたはshortの場合、変換の結果は、最初のステップの結果の型Tへの縮小変換(5.1.3)になります。

doubleからfloatへの縮小変換は、バイナリ浮動小数点形式間の変換に関するIEEE 754のルールによって決定され、最近接丸めポリシー(15.4)が使用されます。この変換では精度だけでなく範囲も失われることがあり、ゼロ以外のdoubleからfloatゼロ、および有限のdoubleからfloat無限大の結果を得る可能性があります。double NaNはfloat NaNに変換され、double無限大は同じ符号が付いたfloat無限大に変換されます。

同じビット数を使用して表される型のペアには、charshortintfloatlongdoubleがあります。ある型から同じビット数で表される別の型に変換すると、型によってビットの使用方法が異なるため、情報が失われる可能性があります。たとえば、charshortは、どちらも16ビットを使用して表される整数型です。どちらの方向の変換でも情報が失われる可能性があるため、charからshortへの変換とshortからcharへの変換の両方が用意されています。charは符号なしであり、shortは符号付きであるため、charshortに、またはshortcharに縮小すると、絶対値失われる可能性があります。Character.MAX_VALUE (65535)からshortに変換すると、-1 ((216 - 1) - 216)になります。-1のshort値をcharに変換すると、65535 (216 - 1)になります。すべてのint値は、ほぼ同じ絶対値のfloat値に変換できるため(変換後の結果の数値は、値の右端のビットの一部のみが失われる可能性があります)、intからfloatへの変換は拡大として分類されます(たとえば、int 123456789をfloatに変換すると、123456792に切り上げられて精度が失われます)。floatからintへの縮小変換が用意されています。これは、float値が大きくなるほどint値として近似されるときに、その大きさの大部分を失う可能性があるためです(たとえば、Float.MAX_VALUEintに変換すると、2147483647になり、数値の大きさが大幅に小さくなります)。

オーバーフローやアンダーフローなどの情報の損失が発生する可能性はありますが、プリミティブの縮小変換で実行時の例外が発生することはありません(11.1.1)。

例5.1.3-1.プリミティブの縮小変換

class Test {
    public static void main(String[] args) {
        float fmin = Float.NEGATIVE_INFINITY;
        float fmax = Float.POSITIVE_INFINITY;
        System.out.println("long: " + (long)fmin +
                           ".." + (long)fmax);
        System.out.println("int: " + (int)fmin +
                           ".." + (int)fmax);
        System.out.println("short: " + (short)fmin +
                           ".." + (short)fmax);
        System.out.println("char: " + (int)(char)fmin +
                           ".." + (int)(char)fmax);
        System.out.println("byte: " + (byte)fmin +
                           ".." + (byte)fmax);
    }
}

このプログラムでは、次の出力が生成されます。

long: -9223372036854775808..9223372036854775807
int: -2147483648..2147483647
short: 0..-1
char: 0..65535
byte: 0..-1

charintおよびlongの結果は予想できるものであり、型の表現可能な最小値および最大値を生成します。

byteおよびshortの結果は、数値の符号と絶対値に関する情報が失われ、精度も失われます。この結果は、最小と最大のintの下位ビットを調べることで理解できます。最小のintは16進数で0x80000000、最大のintは0x7fffffffです。これにより、それらの値の下位16ビットであるshortの結果(つまり、0x00000xffff)が説明されます。同様に、それらの値の下位16ビットであるcharの結果(つまり、'\u0000''\uffff')が説明されます。また、それらの値の下位8ビットであるbyteの結果(つまり、0x000xff)が説明されます。

例5.1.3-2.情報を失うプリミティブの縮小変換

class Test {
    public static void main(String[] args) {
        // A narrowing of int to short loses high bits:
        System.out.println("(short)0x12345678==0x" +
                           Integer.toHexString((short)0x12345678));
        // An int value too big for byte changes sign and magnitude:
        System.out.println("(byte)255==" + (byte)255);
        // A float value too big to fit gives largest int value:
        System.out.println("(int)1e20f==" + (int)1e20f);
        // A NaN converted to int yields zero:
        System.out.println("(int)NaN==" + (int)Float.NaN);
        // A double value too large for float yields infinity:
        System.out.println("(float)-1e100==" + (float)-1e100);
        // A double value too small for float underflows to zero:
        System.out.println("(float)1e-50==" + (float)1e-50);
    }
}

このプログラムでは、次の出力が生成されます。

(short)0x12345678==0x5678
(byte)255==-1
(int)1e20f==2147483647
(int)NaN==0
(float)-1e100==-Infinity
(float)1e-50==0.0

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

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

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

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

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

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

編集部: 5.5項の残りの部分に変更はありません。

5.7 テスト・コンテキスト

ある型の値が比較されるときや、場合によっては別の型に変換されるときに、式のテスト・コンテキストが発生します。テスト・コンテキストでは、型比較演算子(15.20.2)のオペランドを別の型と比較できます。テスト・コンテキストでは、パターン一致演算子(15.20.2)のオペランド、またはswitchブロック(14.11.1)に関連付けられている少なくとも1つのパターンcaseラベルを持つswitch式または文のセレクタ式を比較し、パターン・マッチングのプロセスの一部として型に変換することできます。パターン・マッチングは本質的に条件付きプロセス(14.30.2)であるため、テスト・コンテキストでは、実行時に失敗することや情報が失われることがある変換が使用されると予測されます。

テスト・コンテキストでは、参照型のキャスト・コンテキストと同様の変換が使用されますが、無検査の参照の縮小変換は許可されません(5.1.6.2)。

式がプリミティブ型の場合は、テスト・コンテキストに恒等変換(5.1.1)を使用できます。次のいずれかになります:

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

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

テスト・コンテキストで参照の縮小変換が使用されると、式の値のクラスに対して実行時チェックが実行され、ClassCastExceptionが発生する可能性があります。

Sから型Tへのテスト変換があるかどうかは、情報が失われることなく型Sの値を型Tに変換できるかどうかとは異なります。たとえば、intからbyteへのテスト変換や、ObjectからStringへのテスト変換がありますが、byteとして表すことができないint値は多数あり、Stringのインスタンスを参照しないObject値も多数あります。パターン・マッチングのランタイム・プロセスは、情報の損失が発生するかどうかに敏感であるため、所定の値に対してテスト変換が正確であるという概念に依存します。

5.7.1 正確なテスト変換

値のテスト変換は、情報が失われることや例外がスローされることなく結果を生成する場合に正確です。それ以外の場合は、不正確です。

情報の損失は、正確でないプリミティブの拡大変換(5.1.2)、プリミティブの縮小変換(5.1.3)、またはプリミティブの拡大および縮小変換(5.1.4)のいずれかで発生する可能性があります。損失は次の1つ以上の形態を取ります:

これらのいずれかの変換を適用すると、情報が失われ、それが不具合の潜在的な原因になる可能性があります。たとえば、intの変数iに値1000が格納されている場合、byteへのプリミティブの縮小変換によって結果の-24が生成されます。情報の損失が発生しました: 結果の絶対値と記号の両方が元の値のものと異なっています。そのため、値1000intからbyteへの変換は不正確です。一方、値10intからbyteへの変換は、その結果の10が元の値と同じであるため、正確です。

検査された参照の縮小変換(5.1.6.2)またはボックス化解除変換(5.1.8)で例外が発生する可能性があります。

変換によって情報が失われることがあるか、例外がスローされることがあるかを判断するには、ランタイム・チェックが必要です。情報の損失が発生しないことや、例外がスローされないことがチェックで判断された場合、変換は正確です。それ以外の場合、変換は不正確であり、その結果または例外は変換が発生しなかったものとして破棄されます。

テスト変換が複数の変換で構成されている場合、そのような変換がすべて正確であれば、テスト変換は正確です。それ以外の場合、テスト変換は不正確です。

プリミティブ型Sからプリミティブ型Tへの値の変換によって情報が失われるかどうかを判定するランタイム・チェックは明快ではありません。情報の損失をチェックする明快な方法は、値をSからTに変換してから、結果をTからSに戻して、その最終結果を元の値と比較することです。ただし、このSからTに変換してSに戻す「ラウンド・トリップ」変換は、SからTへの値の元の変換が不正確であったとしても、最終的な結果が元の値と等しくなる可能性があるため、判断を誤る可能性があります。

例として、Integer.MAX_VALUEintからfloatへの変換について考えてみます。結果はyとします。この変換は、最近接丸めポリシーの端数処理が原因で不正確です: 精度が失われます(15.4)。yintに変換して戻した結果のzもゼロの丸めポリシー(4.2.4)に従った端数処理のために不正確になります。この場合、zは元の値Integer.MAX_VALUEと等しくなりますが、元のInteger.MAX_VALUEintからfloatへの変換によってすべての情報が保持されていると判断するのは誤りです。

別の例として、Character.MAX_VALUEcharからshortへの変換について考えてみます。この変換では、符号なしの16ビット値65535を符号付き16ビット値-1に縮小します。絶対値の損失が発生するため、変換は不正確です。char値はゼロからの距離が65535の整数値ですが、short値はゼロからの距離が1のみの整数値です。このshort値をcharに戻す変換も不正確です。これは、符号付き16ビット値-1を符号なし16ビット値65535に縮小するため、絶対値の損失が再び発生します。65535が元の値Character.MAX_VALUEと等しくなっていても、元のcharからshortへのCharacter.MAX_VALUEの変換がすべての値を保持していると結論付けることは誤りです。

情報の損失をチェックするための安全ですが明快でない方法は、値をSからTに変換してから、その結果と元の値の両方をSTのすべての値を表現できる型に昇格させることです。この型は、正確に昇格された型と呼ばれます。SからTへの元の変換で情報が失われた場合、昇格された2つの値は等しくないものとして比較され、情報が失われることが明らかになります。「ラウンドトリップ」で発生することがある、元の変換の不正確さが別の変換の不正確さによって打ち消される可能性がなくなります。

プリミティブ型Sからプリミティブ型Tへのテスト変換の場合、正確に昇格された型は次のように決定されます:

vのプリミティブ型Sからプリミティブ型Tへのテスト変換をCとします。このとき、次のいずれかが成立します:

例5.7.1-1.プリミティブ型の正確なテスト変換

ソース型byteからターゲット型charへの変換について考えてみます。これら2つの型の正確に昇格された型はintです。bytexからcharへの変換が正確かどうかを判断するためのランタイム・チェックは、次の式によって指定されます:

(int)(char) x == (int) x

情報の損失をチェックするために正確に昇格された型を使用し、ラウンドトリップ・キャストを使用しない論拠は、前述したように、ラウンドトリップ・キャストが情報の損失の可能性を明らかにできないことがあるということです。たとえば、bytexからcharへの変換が正確であるかどうかを判断するためにラウンドトリップ・キャストを使用すると、次の式が導かれます:

(byte)(char) x == x

次の最大許容値のxの場合、ラウンドトリップ・キャストはtrueと評価されます:

byte x = 127
(byte)(char) x == x // evaluates to true

次の最小許容値のxの場合、ラウンドトリップ・キャストはtrueと評価しますが誤りです:

byte x = -128
(byte)(char) x == x // also evaluates to true

最初のキャスト変換(char) xは、表現が65408の文字である'タ'と評価されます。次に、65408byteへの2回目のキャスト変換によって戻されると、結果は再び-128になり、等価演算子はtrueと評価されます。

(byte)(char) x // evaluates to -128

ただし、-128の数値は65408の数値と同じではありませんが、モジュロ算術のために2番目の変換の結果はxの元の値と同じ数値になってしまします。この残念な結果として、ユーザーは元の値が保持され、xからcharへの変換が安全であると誤って推測することで、この変換を適用した元の情報を失うようになります。

そのため、正確に昇格された型が使用されます。これにより、元の値xと変換された値(char) xの両方を正確に昇格された型int (より幅が広い型)に変換すると、2つの値が異なることが正しくわかるようになります。したがって、そのような変換は不正確になります。

5.7.2 無条件に正確なテスト変換

テスト・コンテキストで許可されるほとんどの変換は、実行時に変換される値に応じて正確か不正確になります。ただし、一部の変換は値に関係なく常に正確になります。そのような変換は、無条件に正確であると表現されます。コンパイル時に、無条件に正確な変換は実行時に情報の損失や例外のスローなしに結果を生成することがわかっています。無条件に正確な変換は次のとおりです:

たとえば、byteからintへのプリミティブの拡大変換は、常に数値の絶対値に関する情報が失われずに成功するため、無条件に正確です。

これらの変換の一部には、実行時に処置が必要なものがあり、ボックス化変換(5.1.7)の場合はOutOfMemoryErrorで失敗することもあります。ただし、実行時の動作は、変換が無条件に正確であるかどうかには影響しません。

コンパイル時には無条件の正確性を使用して、スイッチ・ブロック(14.11.1)内のあるパターンの優位性と、switchブロック全体が完全かどうか(14.11.1.1)をチェックします。

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

14.11 switch

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

SwitchStatement:
switch ( Expression ) SwitchBlock

Expressionセレクタ式と呼ばれます。セレクタ式の型は、charbyteshortintまたは参照型である必要があり、それ以外の場合はコンパイル時にエラーが発生します任意の型にできます。

セレクタ式に許可される型は、長年にわたって拡張されてきました。Java SE 1.0では、byteshortcharintセレクタ型をサポートしていました。Java SE 5.0では自動ボックス化が導入されたため、switchは列挙とともにラッパー・クラスByteShortCharacterIntegerをサポートするように拡張されました。Java SE 7では、Stringが追加されました。Java SE 21では、caseラベルの型パターンとのパターン・マッチングを考慮するために、参照型が追加されました(プリミティブに対する既存のサポートは維持しています)。Java SE 24では、すべてのプリミティブ型とそのボックスをサポートしていて、case定数によるサポートを拡張してlongやfloatなどを許容しています。switchおよびinstanceof (15.20.2)によって比較とパターン・マッチングができる値に対するすべての制限を解除することで、データ探索を一律に実行できます。

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 {, CasePattern} [Guard]
default
CaseConstant:
ConditionalExpression
CasePattern:
Pattern
Guard:
when Expression

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

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

caseラベルは、case定数の(空でない)リスト、nullリテラル、caseパターンの(空でない)リストのいずれかを持ちます。

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

nullリテラルを持つcaseラベルは、オプションのdefaultを持つことがあります。

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

caseラベルが複数のcaseパターンを持っているときにパターン変数(caseラベルに関連付けられたガードによって宣言された変数を除く)を宣言すると、コンパイル時にエラーが発生します。

caseラベルにパターン変数を宣言するcaseパターンが複数あると、caseラベルが適用された場合に、どの変数が初期化されるかが明確になりません。次に例を示します。

Object obj = ...;
switch (obj) {
  case Integer i, Boolean b -> {
    ...       // Error! Is i or b initialized?
  }
  ...
}

caseパターンのうち1つのみがパターン変数を宣言している場合でも、変数が初期化されたかどうかは明らかではありません。次に例を示します:

Object obj = ...;
switch (obj) {
  case Integer i, Boolean _ -> {
    ...       // Error! Is i initialized?
  }
  ...
}

次の場合はコンパイル時にエラーが発生しません:

Object obj = ...;
switch (obj) {
  case Integer _, Boolean _ -> {
    ...       // Matches both an Integer and a Boolean
  }
  ...
}

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

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

caseラベルに関連付けられたガードは、次のすべての条件を満たす必要があります。そうでない場合は、コンパイル時にエラーが発生します。

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

case定数はセレクタ式との代入互換性があることが必要ですが、caseパターンではテスト・コンテキスト(5.714.30.3)で値を比較および変換できます。

Java SE 24より前のcase定数は、最初の項目で指定された型に制限されていました(charbyteshortなど)。Java SE 24では、有効なcase定数のセットが拡張され、longやfloatなどが許容されます。

longfloatまたはdouble型のセレクタ式には、それぞれlong型の整数リテラル(5L)、float型の浮動小数点リテラル(5f)およびdouble型の浮動小数点リテラル(5d)であるcase定数が必要になります。浮動小数点セレクタ型と整数リテラルが混在することはできません。たとえば、intからfloatへのプリミティブの拡大変換は不可逆です。intからdoubleへのプリミティブの拡大変換は無条件に正確ですが、caseリテラルで暗黙的な変換と明示的な変換を混在させると、複雑な定数式の場合はコードが正しくなくなる可能性があるため、整数リテラルは許可されません。

switchブロックは、booleanlongfloatおよびdouble型と連携するように設計されていません。switch文のセレクタ式またはswitch式には、これらの型のどれも指定できません。

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

セレクタ式がbooleanまたはBoolean型であるswitch文またはswitch式のswitchブロックに、trueという名前のswitchブロック、falseという名前の定数およびdefaultラベルのcase定数が1つ関連付けられている場合、コンパイル時にエラーが発生します。

switchブロック内のswitchラベルは、それが適用されるすべての値に対して前のswitchラベルのいずれかにも適用されると判断できる場合、優先されると見なされます。switchブロック内のどのswitchラベルも優先されない場合、コンパイル時にエラーになります。switchラベルが優先されるかどうかを判断するするためのルールは次のとおりです。

switchブロック内にn (n>1) caseパターンp1, ..., pncaseラベルがあり、パターンの1つpi (1≤i<n)が他のパターンpj (i<j≤n)よりも優先される場合、コンパイル時にエラーが発生します。

次のいずれかに該当する場合は、コンパイル時にエラーが発生します:

使用する場合、defaultラベルはswitchブロックの最後に配置します。

互換性の理由から、defaultラベルは、nullリテラルまたはcaseパターンを持たないcaseラベルの前に指定できます。

int i = ...;
switch(i) {
    default ->
        System.out.println("Some other integer");
    case 42 -> // allowed
        System.out.println("42");
}

使用する場合、case null, defaultラベルはswitchブロックの最後に配置します。

switchラベルが付いた文グループで構成されるswitchブロックで、1つ以上のパターン変数([6.3.3])を宣言する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():                       // No pattern variables declared
        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ブロックは、セレクタ式eについて網羅的です:

case要素のセットPCEは、次のいずれかの場合に型Tカバーします:

通常、レコード・パターンはレコード型の値のサブセットにのみ一致します。ただし、switchブロック内の複数のレコード・パターンを組み合せて、実際にレコード型のすべての値に一致させることができます。次に例を示します。

sealed interface I permits A, B, C {}
final class A   implements I {}
final class B   implements I {}
record C(int j) implements I {}  // Implicitly final
record Box(I i) {}

int testExhaustiveRecordPatterns(Box b) {
    return switch (b) {     // Exhaustive!
        case Box(A a) -> 0;
        case Box(B b) -> 1;
        case Box(C c) -> 2;
    };
}

このswitchブロックが網羅的かどうかを判断するには、レコード・パターンの組合せを分析する必要があります。レコード・パターンBox(I i)を含むセットは型Boxをカバーするため、パターンBox(A a)Box(B b)およびBox(C c)を含むセットは、パターンBox(I i)を含むセットに書き換えることができます。これは、パターンA aB bC cを含むセットがパターンI iに削減されるため(同じセットが型Iをカバーするため)、パターンBox(A a)Box(B b)Box(C c)を含むセットがパターンBox(I i)に削減されます。

ただし、レコード・パターンのセットの書き換えは必ずしも単純ではありません。次に例を示します。

record IPair(I i, I j){}

int testNonExhaustiveRecordPatterns(IPair p) {
    return switch (p) {     // Not Exhaustive!
        case IPair(A a, A a) -> 0;
        case IPair(B b, B b) -> 1;
        case IPair(C c, C c) -> 2;
    };
}

前述の例のロジックを適用して、パターンIPair(A a, A a)IPair(B b, B b)IPair(C c, C c)を含むセットをパターンIPair(I i, I j)を含むセットに書き換えて、switchブロックが型IPairを使い果たすと結論付けたくなります。ただし、これは正しくありません。たとえば、switchブロックには、最初のコンポーネントがA値で、2番目のコンポーネントがB値であるIPair値と一致するラベルが実際にはないためです。あるコンポーネントのレコード・パターンを組み合せることは、その他のコンポーネントの同じ値と一致する場合にのみ有効です。たとえば、3つのレコード・パターンIPair(A a, I i)IPair(B b, I i)およびIPair(C c, I i)を含むセットは、パターンIPair(I j, I i)に削減できます。

switch文または式は、そのswitchブロックがセレクタ式に対して網羅的である場合、網羅的になります。

14.11.1.2 実行時に適用されるswitchラベルの決定

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

  1. Tが参照型で、もし値がnull参照の場合は、nullリテラルを含むcaseラベルが適用されます。

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

    • Tが参照型のときに値がnull参照でない場合は、値が最初にボックス化解除変換(5.1.8)の対象になり、定数cがボックス化解除された値と等しい場合、case定数cあるcaseラベルがCharacterByteShortまたはIntegerLongFloatDoubleまたはBoolean型の値に適用されます。

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

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

    • Tがプリミティブ型の場合、case定数がcあるcaseラベルは、charbyteshortintまたはlongfloatdoublebooleanまたはString型の値または列挙型(定数cが値と等しい場合)に適用されます。

      等価性は、整数型およびブール型の場合は==演算子(15.21)および浮動小数点型の場合は表現等価性(java.lang.Double)によって定義されます。ただし値がString場合その場合は等価性はクラスStringequalsメソッドによって定義されます。

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

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

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

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

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

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

    • caseパターンp1、...、pn (n≥1)を持つcaseラベルが値に適用されることを確認するには、値に適用される最初のcaseパターンpi (1≤i≤n)を見つけます(存在する場合)。

      caseパターンが値に適用されるかどうかを判断するには、まず、値がパターン(14.30.2)に一致するかどうかをチェックします。その場合:

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

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

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

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

        それ以外の場合は、その結果の値がtrueのときに、caseパターンが適用されます。

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

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

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

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

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

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または列挙型ではない、または(ii) switchブロックに関連付けられたcaseパターンまたはnullリテラルが存在しているもののことです。

Java SE 24より前は、charbyteshortint、およびswitchの有効なセレクタ型としてサポートされている参照型のみです。この制限はJava SE 24で解除されているため、セレクタ型がfloatdoublelongbooleanまたはそれらいずれかのラッパー型のswitch文も拡張されたものとみなされます。

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

Java SE 21より前のswitch文(およびswitch式)は、(i)セレクタ式の型が整数型(longを除く)、列挙型またはStringに制限されており、(ii) case nullラベルはサポートされていませんでした。さらに、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 21では、caseパターンのサポートに加えて、前述のswitch文(およびswitch式)の2つの制限が緩和され、(i)任意の参照型のセレクタ式を許可し、(ii)nullリテラルを持つcaseラベルを許可するようになりました。また、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.30 パターン

14.30.1 パターンの種類

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

Pattern:
TypePattern
RecordPattern
TypePattern:
LocalVariableDeclaration
RecordPattern:
ReferenceType ( [ComponentPatternList] )
ComponentPatternList:
ComponentPattern {, ComponentPattern }
ComponentPattern:
Pattern
MatchAllPattern
MatchAllPattern:
_

便宜上、ここでは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)レコード・パターンのコンポーネント・パターン・リストに直接示されている場合、または(2)レコード・パターンのコンポーネント・パターン・リストに直接示されているレコード・パターンにネストされている場合、パターンはレコード・パターンにネストされます。レコード・パターンにネストされていない場合、パターンは最上位です。

型パターンは、パターン変数として知られる1つのローカル変数を宣言します。宣言に識別子が含まれている場合、これはパターン変数の名前を指定します。それ以外の場合、パターン変数は名前のないパターン変数と呼ばれます。

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

最上位型パターンで宣言されたパターン変数の型は、LocalVariableTypeによって示される参照型です。

ネストされた型パターンで宣言されたパターン変数の型は、次のように決定されます。

型パターンは、型Rのレコード・パターンのコンポーネント・パターン・リストに直接示される場合、null一致と呼ばれます。ここで、Rの対応するレコード・コンポーネントは型UUは参照型であり、型パターンは型Uに対して無条件です(14.30.3)。

この型パターンのコンパイル時プロパティは、パターン・マッチング(14.30.2)の実行時プロセスで使用されるため、実行時に使用する型パターンに関連付けられています。

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

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

ReferenceType (またはその一部)に注釈が付けられていると、コンパイル時にエラーが発生します。

Javaプログラミング言語の将来のバージョンでは、この注釈に対する制限が解除される可能性があります。

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

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

レコード・パターンでは、パターン変数自体を直接宣言しませんが、コンポーネント・パターン・リスト内のパターン変数の宣言を含めることができます。

レコード・パターンに同じ名前のパターン変数の宣言が複数含まれていると、コンパイル時にエラーが発生します。

match-allパターンは、パターン変数を宣言しない特殊なパターンであり、レコード・パターンrのコンポーネント・パターン・リストにのみ直接示すことができます。

Rをレコード・パターンrの型とし、TRの対応するコンポーネント・フィールドの型(8.10.3)とします。match-allパターンの型は、Tで指定しているすべての合成型変数に関するTの上方投影です。

match-allパターンは、名前なしパターン変数を宣言し、LocalVariableTypevarであるネストした型パターンと同等であることがわかります。

14.30.2 パターン・マッチング

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

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

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

14.30.3 パターンのプロパティ

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

パターンpは、Tのすべての値がp一致することがある一致する場合に型Tに対して無条件と表現されます。そのため、パターン・マッチングのテストの側面は排除できますそのため、実行時のパターン・マッチングに処置は不要になります。次のように定義されます:

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

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

第15章: 式

15.5 式と実行時のチェック

式の型がプリミティブ型の場合、式の値は同じプリミティブ型になります。

式の型が参照型の場合、参照されるオブジェクトのクラスや、その値がnullではなくオブジェクトへの参照であるかどうかは、コンパイル時にはわからないことがあります。

Javaプログラミング言語には、参照されるオブジェクトの実際のクラスまたはプリミティブ型の値が、式の型からは推論できない方法でプログラムの実行に影響を与える場所がいくつかあります。パラメータは次のとおりです。

オブジェクトのクラスが静的に認識されていない状況では、実行時の型エラーが発生する可能性があります。

また、実行時に静的に認識されいる型が正確でない場合もあります。そのような状況は、コンパイル時に無検査の警告が発生するプログラムで発生する可能性があります。このような警告は、静的に安全が保証できない操作と、非具象化可能型(4.7)が含まれるためにすぐに動的チェックの対象にできない操作に応じて提示されます。その結果として、プログラムの実行の後半で動的チェックを行うと、不整合が検出されて実行時の型エラーが発生する可能性があります。

実行時の型エラーは、次の状況でのみ発生する可能性があります:

15.16 Cast式

キャスト式は、実行時にある数値型の値を別の数値型の同様の値に変換します。または、コンパイル時に式の型がbooleanであることを確認します。または、実行時に参照値が指定された参照型または参照型のリストと互換性のあるクラスを持つオブジェクトを指しているか、プリミティブ型の値を実装するオブジェクトを指しているかをチェックします。

CastExpression:
( PrimitiveType ) UnaryExpression
( ReferenceType {AdditionalBound} ) UnaryExpressionNotPlusMinus
( ReferenceType {AdditionalBound} ) LambdaExpression #

便宜上、ここでは[4.4]の次のプロダクションを示します:

AdditionalBound:
& InterfaceType

括弧と括弧内に含まれる型または型のリストは、キャスト演算子と呼ばれることもあります。

キャスト演算子に型のリストが含まれている(つまり、ReferenceTypeの後に1つ以上のAdditionalBound項が続く)場合は、次のすべてに当てはまる必要があります。それ以外の場合は、コンパイル時にエラーが発生します:

キャスト式によって導入されたキャスト・コンテキストのターゲット型(5.5)は、キャスト演算子に示されるPrimitiveTypeまたはReferenceType (その後にAdditionalBound項が続いていない場合)か、キャスト演算子に示されるReferenceTypeAdditionalBound項が表す交差型です。

キャスト式の型は、このターゲット型に取得変換(5.1.10)を適用した結果です。

キャストは、ラムダ式またはメソッド参照式に特定のターゲット型を明示的に"タグ付け"するために使用できます。適度の柔軟性を提供するために、交差が関数インタフェース(9.8)を導入する場合、ターゲット型は交差型を示す型のリストにすることもできます。

キャスト式の結果は、オペランド式を評価した結果が変数であっても、変数ではなく値になります。

キャスト演算子で指定されたターゲット型へのキャスト変換(5.5)ではオペランドのコンパイル時の型が変換できない場合は、コンパイル時にエラーが発生します。

それ以外の場合は、キャスト演算子で指定されたターゲット型へのキャスト変換によって、実行時にオペランド値が変換されます(必要な場合)。

実行時にキャストが許容されない場合は、ClassCastExceptionがスローされます。

一部のキャストでは、コンパイル時にエラーが発生します。一部のキャストは、実行時に常に正しいことをコンパイル時に証明できます。たとえば、クラス型の値をそのスーパークラスの型に変換することは常に正しく、このようなキャストでは実行時に特別な処置が不要になります。最後に、一部のキャストは、コンパイル時に常に正しいか、常に正しくないかを証明できません。そのようなキャストでは、実行時にテストが必要になります。詳細は、5.5を参照してください。

キャスト式の結果が正確に同じ数値である場合や、ClassCastExceptionがスローされなかった場合、変換は正確 (5.7.1)と特徴付けられます。

15.20 関係演算子

15.20.2 instanceof演算子

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

InstanceofExpression:
RelationalExpression instanceof ReferenceType
RelationalExpression instanceof Pattern

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

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

RelationalExpressionの型は、参照型、プリミティブ型またはNULL型を指定できます。

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

Java SE 24より前では、instanceof演算子は、参照型間のチェックされたキャスト互換可能性のみを検査していました。可能性がある変換は、チェックされたa)恒等変換、b)拡張参照変換、c)縮小参照変換でした。Java SE 24より前では、RelationalExpressionの型は、チェックされたキャスト互換可能性の検証にのみ使用されていましたが、Java SE 24では、RelationalExpressionの型は、RelationalExpressionの型がコンパイル時に推論された型であり、消去された型ではないことを実行時に検証するためにも使用されます。この推論は、プリミティブ型も含まれるようになったテスト変換の選択時にも重要です。次に例を示します。

List<Short> s = ...
if (s.get(0) instanceof int) { ... }

この例では、推論された型はShortで、消去された型はObjectです。instanceofは、Shortからintへのテスト変換(ボックス化解除変換と拡張プリミティブ変換(の順序)で構成される)を保護し、有効なテスト変換であるObjectからintへのテスト変換(縮小参照変換とボックス化解除変換で構成される)を保護しません。その結果、推論される型Shortが、Shortからintへのテスト変換に使用されます。

次のルールは、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セレクタ式と呼ばれます。セレクタ式の型は、charbyteshortintまたは参照型である必要があります。それ以外の場合は、どの型でもコンパイル時にエラーが発生します。

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}
case null [, default]
case CasePattern {, CasePattern} [Guard]
default
CaseConstant:
ConditionalExpression
CasePattern:
Pattern
Guard:
when Expression

第19章: 構文

この章では、第4章、第6章から第10章、第14章、第15章に示した構文文法と、第3章の字句文法の主要部分を、2.4の表記法を使用して繰り返します。

InstanceofExpressionのプロダクションのみが変更されています。この項の残りの部分に変更はありません。

15のプロダクション

InstanceofExpression:
RelationalExpression instanceof ReferenceType
RelationalExpression instanceof Pattern