このドキュメントでは、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プログラミング言語と特別な関係があります。例として、Object
、Class
、ClassLoader
、String
および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つの方法で容易になります:
まず、多相的な式(15.2)と呼ばれる一部の式では、推論される型がターゲット型の影響を受ける可能性があります。異なるコンテキストでは、同じ式に異なる型を指定できます。
次に、式の型が推論された後、式の型からターゲット型への暗黙的な変換を実行できる場合があります。
どちらの方法でも適切な型を生成できない場合は、コンパイル時にエラーが発生します。
式が多相的な式かどうかを決定するルールと、多相的な式の場合の特定のコンテキストでの型と互換性は、コンテキストの種類と式の形式によって異なります。ターゲット型は、式の型に影響を与えることに加えて、適切な型の値を生成するために式の実行時の動作に影響を与えることがあります。
同様に、ターゲット型で暗黙的な変換を許可するかどうかを決定するルールは、コンテキストの種類、式の型、および1つの特殊なケースでは定数式の値(15.29)によって異なります。型Sから型Tへの変換により、コンパイル時に型Sの式を型Tであるかのように処理できます。場合によっては、変換の妥当性をチェックするためや、式の実行時の値を新しい型Tに適した形式に変換するために、実行時に対応する処置が必要になることがあります。
例5.0-1.コンパイル時および実行時の変換
型
Object
から型Thread
への変換では、実行時の値が実際にクラスThread
またはそのサブクラスの1つのインスタンスであることを確認するための実行時のチェックが必要です。そうでない場合は例外がスローされます。型
Thread
から型Object
への変換に、実行時の処置は必要ありません。Thread
はObject
のサブクラスであるため、Thread
型の式によって生成されるすべての参照は、Object
型の有効な参照値です。int
型からlong
型への変換には、32ビット整数値から64ビットlong
表現への実行時の符号拡張が必要です。失われる情報はありません。double
型からlong
型への変換では、64ビットの浮動小数点値から64ビットの整数表現への自明でない変換が必要です。実際の実行時の値によっては、情報が失われることがあります。
Javaプログラミング言語で可能な変換は、いくつかの広範なカテゴリに分類されます:
- 恒等変換
- プリミティブの拡大変換
- プリミティブの縮小変換
- 参照の拡大変換
- 参照の縮小変換
- ボクシング変換
- アンボクシング変換
- 無検査変換
- 取得変換
- 文字列変換
多相的な式がコンテキストの影響を受ける可能性がある、または暗黙的な変換が発生する可能性のある変換コンテキストには7つの種類があります。コンテキストの種類ごとに、多相的な式の型付けのルールが異なり、前述の一部のカテゴリでは変換できますが、別のカテゴリでは変換できません。コンテキストは次のとおりです:
割当てコンテキスト(5.2、15.26)。式の値が名前付き変数にバインドされます。プリミティブ型と参照型は拡大の対象になり、値はボックス化またはボックス化解除されることがあり、一部のプリミティブ定数式は縮小の対象となることがあります。無検査変換が発生することもあります。
厳密な呼出しコンテキスト(5.3、15.9、15.12)。引数はコンストラクタまたはメソッドの仮パラメータにバインドされます。プリミティブの拡大変換、参照の拡大変換、無検査変換が発生する可能性があります。
緩い呼出しコンテキスト(5.3、15.9、15.12)は、厳密な呼出しコンテキストと同様に、引数は仮パラメータにバインドされます。メソッドまたはコンストラクタの呼出しは、厳密な呼出しコンテキストのみを使用すると適用可能な宣言が見つからない場合に、このコンテキストを提供することがあります。このコンテキストでは、拡大変換および無検査変換に加えて、ボックス化およびボックス化解除の変換を実行できます。
キャスト・コンテキスト(5.5)。式の値が、キャスト演算子(15.16)によって明示的に指定された型に変換されます。キャスト・コンテキストは、割当てコンテキストや緩い呼出しコンテキストよりも包括的で、文字列変換以外の特定の変換が可能です。ただし、参照型への特定のキャストは、実行時に正確性が検査されます。
数値コンテキスト(5.6)。数値演算子のオペランドまたは数値を操作するその他の式のオペランドは、共通型に拡張できます。
テスト・コンテキスト(5.7)。式の値が比較され、場合によっては型比較演算子(15.20.2)または
パターン(14.30)パターン・マッチング(14.30.2)によって明示的に指定された型に変換されます。テスト・コンテキストは、割当てコンテキストや緩い呼出しコンテキストよりも包括的ですが、キャスト・コンテキストほど包括的ではありません。
「変換」という用語は、特定のコンテキストで許可される変換を具体的にせずに記述するためにも使用されます。たとえば、ローカル変数のイニシャライザである式が「割当て変換」の対象であると表現した場合は、割当てコンテキストのルールに従って、特定の変換がその式に暗黙的に選択されることを意味します。別の例として、式が「キャスト変換」されると表現した場合は、式の型がキャスト・コンテキストで許可されたとおりに変換されることを意味します。
例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の特定の変換は、プリミティブの拡大変換と呼ばれます:
byte
からshort
、int
、long
、float
またはdouble
short
からint
、long
、float
またはdouble
char
からint
、long
、float
またはdouble
int
からlong
、float
またはdouble
long
からfloat
またはdouble
float
からdouble
数値の全体的な絶対値に関する情報が失われない拡大プリミティブ変換数値が正確に保たれる次の場合は、正確な拡大プリミティブ変換と呼ばれ、数値は正確に保持されます。このような変換は、次のいずれかになります:
整数型から別の整数型
byte
、short
またはchar
から浮動小数点型int
からdouble
float
からdouble
int
からfloat
、long
からfloat
またはlong
からdouble
への拡大プリミティブ変換は、制度が失われることがあります。つまり、値の右端ビットの一部が失われる結果を招く可能性があります。この場合、結果の浮動小数点値は、最近接丸めポリシー(15.4)を使用して適切に丸められたバージョンの整数値になります。
符号付き整数値から整数型Tへの拡大変換は、より幅が広い形式を満たすために単に整数値の2の補数表現を符号拡張します。
char
から整数型Tへの拡大変換は、より幅が広い形式を満たすためにchar
値の表現をゼロ拡張します。
int
からfloat
、long
からfloat
、int
から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の特定の変換は、プリミティブの縮小変換と呼ばれます:
short
からbyte
またはchar
char
からbyte
またはshort
int
からbyte
、short
またはchar
long
からbyte
、short
、char
またはint
float
からbyte
、short
、char
、int
またはlong
double
からbyte
、short
、char
、int
、long
またはfloat
プリミティブの縮小変換では、数値の全体的な絶対値に関する情報が失われ、精度と範囲も失われる可能性があります。
符号付き整数を整数型Tに縮小変換すると、右端のn個のビットを除くすべてのビットが単に破棄されます。このnは、型Tを表すために使用されるビット数です。数値の絶対値に関する情報が失われる可能性に加えて、結果の値の符号が入力値の符号と異なることもあります。
char
を整数型Tに縮小変換すると、同様に右端のn個のビットを除くすべてのビットが単に破棄されます。このnは、型Tを表すために使用されるビット数です。数値の絶対値に関する情報が失われる可能性に加えて、charが16ビットの符号なし整数値を表す場合でも、結果の値は負の数値になることがあります。
浮動小数点数を整数型Tに縮小変換するには、次の2つのステップを実行します:
最初のステップでは、浮動小数点数が
long
(Tがlong
の場合)またはint
(Tがbyte
、short
、char
またはint
の場合)に変換されます。浮動小数点数がNaN (4.2.3)の場合、変換の最初のステップの結果は、
int
またはlong
0
になります。それ以外の場合、浮動小数点数が無限大でない場合、浮動小数点値はゼロ丸めポリシー(4.2.4)に従って整数値Vに丸められます。その次には、2つのケースがあります:
Tが
long
のときに、この整数値がlong
として表すことができる場合、最初のステップの結果はlong
値Vになります。それ以外の場合、この整数値が
int
として表すことができる場合、最初のステップの結果はint
値Vになります。
それ以外の場合は、次の2つのケースのいずれかに当てはまる必要があります:
値は小さすぎるもの(大きな負の値または負の無限大)であることが必要で、最初のステップの結果は、型
int
または型long
で表現可能な最小値になります。値は大きすぎる(大きな正の値または正の無限大)ものであることが必要で、最初のステップの結果は、型
int
または型long
で表現可能な最大値になります。
2番目のステップ:
Tが
int
またはlong
の場合、変換の結果は最初のステップの結果になります。Tが
byte
、char
またはshort
の場合、変換の結果は、最初のステップの結果の型Tへの縮小変換(5.1.3)になります。
double
からfloat
への縮小変換は、バイナリ浮動小数点形式間の変換に関するIEEE 754のルールによって決定され、最近接丸めポリシー(15.4)が使用されます。この変換では精度だけでなく範囲も失われることがあり、ゼロ以外のdouble
からfloat
ゼロ、および有限のdouble
からfloat
無限大の結果を得る可能性があります。double
NaNはfloat
NaNに変換され、double
無限大は同じ符号が付いたfloat
無限大に変換されます。
同じビット数を使用して表される型のペアには、
char
とshort
、int
とfloat
、long
とdouble
があります。ある型から同じビット数で表される別の型に変換すると、型によってビットの使用方法が異なるため、情報が失われる可能性があります。たとえば、char
とshort
は、どちらも16ビットを使用して表される整数型です。どちらの方向の変換でも情報が失われる可能性があるため、char
からshort
への変換とshort
からchar
への変換の両方が用意されています。char
は符号なしであり、short
は符号付きであるため、char
をshort
に、またはshort
をchar
に縮小すると、絶対値失われる可能性があります。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_VALUE
をint
に変換すると、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
char
、int
およびlong
の結果は予想できるものであり、型の表現可能な最小値および最大値を生成します。
byte
およびshort
の結果は、数値の符号と絶対値に関する情報が失われ、精度も失われます。この結果は、最小と最大のint
の下位ビットを調べることで理解できます。最小のint
は16進数で0x80000000
、最大のintは0x7fffffff
です。これにより、それらの値の下位16ビットであるshort
の結果(つまり、0x0000
と0xffff
)が説明されます。同様に、それらの値の下位16ビットであるcharの結果(つまり、'\u0000'
と'\uffff'
)が説明されます。また、それらの値の下位8ビットであるbyteの結果(つまり、0x00
と0xff
)が説明されます。
例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で定義されている変換をさらに多用できるとともに、これらの変換の組合せをより多く使用できます。
式がプリミティブ型の場合は、キャスト・コンテキストに次のいずれかを使用できます。
恒等変換(5.1.1)
拡張プリミティブ変換(5.1.2)
縮小プリミティブ変換(5.1.3)
拡張および縮小プリミティブ変換(5.1.4)
ボックス化変換(5.1.7)
ボックス化変換後の拡張参照変換(5.1.5)
式が参照型の場合は、キャスト・コンテキストに次のいずれかを使用できます。
恒等変換(5.1.1)
拡張参照変換(5.1.5)
拡張参照変換後のボックス化解除変換
拡張参照変換後のボックス化解除変換に続く拡張プリミティブ変換
縮小参照変換(5.1.6)
縮小参照変換後のボックス化解除変換
ボックス化解除変換(5.1.8)
ボックス化解除変換後の拡張プリミティブ変換
式にNULL型がある場合は、式は任意の参照型にキャストされることがあります。
キャスト・コンテキストで、検査されているか部分的に検査されていない参照の縮小変換(5.1.6.2、5.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)を使用できます。次のいずれかになります:
式が参照型の場合は、テスト・コンテキストに次のいずれかを使用できます:
拡張参照変換後のボックス化解除変換
拡張参照変換後のボックス化解除変換に続く拡張プリミティブ変換
- チェックされる参照の縮小変換(5.1.6.2)
チェックされた後にボックス化解除変換が続く参照の縮小変換
ボックス化解除変換(5.1.8)
ボックス化解除変換後の拡張プリミティブ変換
式に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つ以上の形態を取ります:
- 絶対値の損失(ゼロからのプリミティブ値までの距離)
- 精度の損失(プリミティブ値の右端のビット)
- 範囲の損失(プリミティブ値の
MIN_VALUE
とMAX_VALUE
) - 符号の損失(プリミティブ値は変換後に反転符号付きで示されるか、符号がなくなります)
これらのいずれかの変換を適用すると、情報が失われ、それが不具合の潜在的な原因になる可能性があります。たとえば、
int
の変数i
に値1000
が格納されている場合、byte
へのプリミティブの縮小変換によって結果の-24
が生成されます。情報の損失が発生しました: 結果の絶対値と記号の両方が元の値のものと異なっています。そのため、値1000
のint
からbyte
への変換は不正確です。一方、値10
のint
からbyte
への変換は、その結果の10
が元の値と同じであるため、正確です。
検査された参照の縮小変換(5.1.6.2)またはボックス化解除変換(5.1.8)で例外が発生する可能性があります。
変換によって情報が失われることがあるか、例外がスローされることがあるかを判断するには、ランタイム・チェックが必要です。情報の損失が発生しないことや、例外がスローされないことがチェックで判断された場合、変換は正確です。それ以外の場合、変換は不正確であり、その結果または例外は変換が発生しなかったものとして破棄されます。
テスト変換が複数の変換で構成されている場合、そのような変換がすべて正確であれば、テスト変換は正確です。それ以外の場合、テスト変換は不正確です。
プリミティブ型Sからプリミティブ型Tへの値の変換によって情報が失われるかどうかを判定するランタイム・チェックは明快ではありません。情報の損失をチェックする明快な方法は、値をSからTに変換してから、結果をTからSに戻して、その最終結果を元の値と比較することです。ただし、このSからTに変換してSに戻す「ラウンド・トリップ」変換は、SからTへの値の元の変換が不正確であったとしても、最終的な結果が元の値と等しくなる可能性があるため、判断を誤る可能性があります。
例として、
Integer.MAX_VALUE
のint
からfloat
への変換について考えてみます。結果はy
とします。この変換は、最近接丸めポリシーの端数処理が原因で不正確です: 精度が失われます(15.4)。y
をint
に変換して戻した結果のz
もゼロの丸めポリシー(4.2.4)に従った端数処理のために不正確になります。この場合、z
は元の値Integer.MAX_VALUE
と等しくなりますが、元のInteger.MAX_VALUE
のint
からfloat
への変換によってすべての情報が保持されていると判断するのは誤りです。
別の例として、
Character.MAX_VALUE
のchar
から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に変換してから、その結果と元の値の両方をSとTのすべての値を表現できる型に昇格させることです。この型は、正確に昇格された型と呼ばれます。SからTへの元の変換で情報が失われた場合、昇格された2つの値は等しくないものとして比較され、情報が失われることが明らかになります。「ラウンドトリップ」で発生することがある、元の変換の不正確さが別の変換の不正確さによって打ち消される可能性がなくなります。
プリミティブ型Sからプリミティブ型Tへのテスト変換の場合、正確に昇格された型は次のように決定されます:
SとTが同じプリミティブ型の場合、正確に昇格された型はSになります。
Sが
byte
またはshort
、Tがchar
、またはその逆の場合、正確に昇格された型はint
です。Sが
int
、Tがfloat
、またはその逆の場合、正確に昇格された型はdouble
です。Sが
long
、Tがfloat
またはdouble
、またはその逆の場合、正確に昇格された型はjava.math.BigDecimalです。それ以外の場合、正確に昇格された型はSとTのうち幅が広い方です。BからA (5.1.2)へのプリミティブの拡大変換がある場合、型Aは型Bよりも幅が広くなります。
値vのプリミティブ型Sからプリミティブ型Tへのテスト変換をCとします。このとき、次のいずれかが成立します:
SとTが両方とも浮動小数点型であり、vがゼロ、無限大、
NaN
のいずれかである場合、Cは正確です。Sが浮動小数点型、Tが整数型、vが負のゼロ、無限大、
NaN
のいずれかである場合、Cは不正確です。たとえば、vが整数型に変換される負の浮動小数点ゼロの場合、結果はゼロになります。元の値と比較して、結果では符号の情報が失われています。
それ以外の場合、PはSとTの正確に昇格された型を示すものとし、wは変換Cの結果を示すとします。その場合:
xは、値vのSからPへの変換Dの結果を示すとします。計算は次のようになります:
Pがプリミティブ型の場合、xはSからPへのキャスト変換の結果になります。
Pがjava.math.BigDecimalの場合、xは
new BigDecimal(v)
の実行によって得られた結果になります。
yは値wのTからPへの変換Eの結果を示すとします。計算は次のようになります:
Pがプリミティブ型の場合、yはTからPへのキャスト変換の結果になります。
Pがjava.math.BigDecimalの場合、yは
new BigDecimal(w)
の実行によって得られた結果になります。
Cは、次のいずれかが成立する場合に正確です:
Pは整数型で、xとyは数値等価演算子
==
(15.21.1)に従って等しい。Pは浮動小数点型で、xとyは
Double.compare(x, y) == 0
(java.lang.Double.compare)の実行によって得られた結果に従って等しい。Pはjava.math.BigDecimalで、xとyは
x.compareTo(y) == 0
(java.math.BigDecimal.compareTo)の実行によって得られた結果に従って等しい。
それ以外の場合、Cは不正確です。
次の図は、変換C、DおよびEの概要を示しています:
x -- equal? -- y (P) (P) | | ^ ^ D E ^ ^ | | v ---- C ----> w (S) (T)
例5.7.1-1.プリミティブ型の正確なテスト変換
ソース型byte
からターゲット型char
への変換について考えてみます。これら2つの型の正確に昇格された型はint
です。byte
値x
からchar
への変換が正確かどうかを判断するためのランタイム・チェックは、次の式によって指定されます:
(int)(char) x == (int) x
情報の損失をチェックするために正確に昇格された型を使用し、ラウンドトリップ・キャストを使用しない論拠は、前述したように、ラウンドトリップ・キャストが情報の損失の可能性を明らかにできないことがあるということです。たとえば、byte
値x
から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
の文字である'タ'と評価されます。次に、65408
がbyte
への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はセレクタ式と呼ばれます。セレクタ式の型は、任意の型にできます。char
、byte
、short
、int
または参照型である必要があり、それ以外の場合はコンパイル時にエラーが発生します
セレクタ式に許可される型は、長年にわたって拡張されてきました。Java SE 1.0では、
byte
、short
、char
、int
セレクタ型をサポートしていました。Java SE 5.0では自動ボックス化が導入されたため、switchは列挙とともにラッパー・クラスByte
、Short
、Character
、Integer
をサポートするように拡張されました。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ルール・ブロックまたはswitchルールthrow
文を導入します。またはSwitchラベルが付いた文グループ。これは
:
を使用して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ブロックの場合は、次の両方すべてに当てはまる必要があり、そうでない場合はコンパイル時にエラーが発生します:
switchブロックに関連付けられている2つの
case
定数が同じ値を持つことがない。switchブロックに関連付けられている
null
リテラルは1つのみ。default
ラベルが1つのみ、switchブロックに関連付けられている。
case
ラベルに関連付けられたガードは、次のすべての条件を満たす必要があります。そうでない場合は、コンパイル時にエラーが発生します。
ガードの型は、
boolean
またはBoolean
であることが必要です。使用されていてもガードで宣言されていないローカル変数、仮パラメータまたは例外パラメータは、
final
または事実上のfinal (4.12.4)であることが必要です。ガードで使用されていても宣言されていない空白の
final
変数は、ガードの前に明確に割り当てる(16)ことが必要です。ガードは、値が
false
の定数式(15.29)にすることはできません。
次のすべてに該当する場合、switch
文またはswitch
式のswitchブロックは、セレクタ式T型とのswitch互換性があります:
null
リテラルがswitchブロックに関連付けられている場合、Tは参照型である。列挙定数を指名するswitchブロックに関連付けられた各
case
定数について、case
定数の型はTとの代入互換性がある(5.2)。
- 定数式であるswitchブロックに関連付けられている各
case
定数について、定数はTとの代入互換性があり、Tはchar
、byte
、short
、int
、Character
、Byte
、Short
、Integer
またはString
のいずれかである。
定数式であるswitchブロックに関連付けられた各
case
定数について、次のいずれかに当てはまる:Tが
char
、byte
、short
、int
、Character
、Byte
、Short
、Integer
またはString
のいずれかである場合、定数にはTとの代入互換性がある。Tが
long
、float
、double
またはboolean
のいずれかである場合、case定数の型はTである。Tが
Long
、Float
、Double
またはBoolean
のいずれかである場合、ケース定数の型は、それぞれlong
、float
、double
またはboolean
である。
case
定数はセレクタ式との代入互換性があることが必要ですが、case
パターンではテスト・コンテキスト(5.7、14.30.3)で値を比較および変換できます。
Java SE 24より前の
case
定数は、最初の項目で指定された型に制限されていました(char
、byte
、short
など)。Java SE 24では、有効なcase
定数のセットが拡張され、longやfloatなどが許容されます。
long
、float
またはdouble
型のセレクタ式には、それぞれlong
型の整数リテラル(5L
)、float
型の浮動小数点リテラル(5f
)およびdouble
型の浮動小数点リテラル(5d
)であるcase
定数が必要になります。浮動小数点セレクタ型と整数リテラルが混在することはできません。たとえば、int
からfloat
へのプリミティブの拡大変換は不可逆です。int
からdouble
へのプリミティブの拡大変換は無条件に正確ですが、case
リテラルで暗黙的な変換と明示的な変換を混在させると、複雑な定数式の場合はコードが正しくなくなる可能性があるため、整数リテラルは許可されません。
- switchブロックに関連付けられているすべてのパターンpは、型T (14.30.3)に適用できます。
switchブロックは、
boolean
、long
、float
および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ブロック内に、
case
パターンpを持つガードされていないcase
ラベルが先行し、pがq (14.30.3)より優先される場合、case
パターンqを持つcase
ラベルは優先されません。パターンが別のパターンより優先される定義は型に基づきます。たとえば、型パターン
Object
o
は型パターンString
s
より優先されるため、次の結果はコンパイル時エラーになります:Object obj = ... switch (obj) { case Object o -> System.out.println("An object"); case String s -> // Error! System.out.println("A string"); }
case
パターンを持つガード付きcase
ラベルよりも、同じパターンを持つガードのないcase
ラベルが優先されます。たとえば、次はコンパイル時にエラーになります。String str = ...; switch (str) { case String s -> System.out.println("A string"); case String s when s.length() == 2 -> // Error! System.out.println("Two character string"); ... }
一方、
case
パターンを持つガード付きcase
ラベルは、同じcase
パターンを持つガード付きでないcase
ラベルより優先されるとみなされません。これにより、次の一般的なパターン・プログラミング・スタイルが可能になります。Integer j = ...; switch (j) { case Integer i when i <= 0 -> System.out.println("Less than or equal to zero"); case Integer i -> System.out.println("An integer"); }
唯一の例外は、ガードが値
true
を含む定数式の場合です。次に例を示します。Integer j = ...; switch (j) { case Integer i when true -> // Ok System.out.println("An integer"); case Integer i -> // Error! System.out.println("An integer"); }
複数の
case
パターンを持つcase
ラベルは、それらのパターンのいずれかが、先行するガードされていないcase
ラベルでcase
パターンとして出現するパターンより優先される場合に優先されます。そのため、次のものはコンパイル時にエラーが発生します(型パターンInteger
_
は型パターンNumber
_
より優先されるため):Object obj = ... switch (obj) { case Number _ -> System.out.println("A Number"); case Integer _, String _ -> // Error - dominated! System.out.println("An Integer or a String"); ... }
次のいずれかが保持されている場合、
case
定数cを持つcase
ラベルは優先されません。cはプリミティブ型Sの定数式であり、switchブロックには先行する
case
ラベルがあり、ガードされていないcase
パターンpがあります。このpはSのラッパー・クラスでは制限されていません(14.30.3)。cは参照型Tの定数式であり、switchブロックには先行する
case
ラベルがあり、ガードされていないcase
パターンpがあります。ここで、pは型Tでは制限されていません。cは、enumクラスEの列挙定数を指名します。switchブロックには先行する
case
ラベルがあり、ガードされていないcase
パターンpがあります。このpは型Eに対して無条件です。
たとえば、
Integer
型パターンを持つcase
ラベルは、整数リテラルを持つcase
ラベルより優先されます。Integer j = ...; switch (j) { case Integer i -> System.out.println("An integer"); case 42 -> // Error - dominated! System.out.println("42!"); }
case
パターンp (pがセレクタ式の型(14.30.3)に対して無条件)で、switchブロック内に先行するガードされていないcase
ラベルがある場合は、default
ラベルまたはcase
null,
default
ラベルが優先されます。セレクタ式の型で無条件である
case
パターンを持つcase
ラベルは、名前が示すように、すべての値に一致するため、default
ラベルのように動作します。switchブロックは、default
のように動作する複数のswitchラベルを持つことはできません。
switchブロック内にn (n>1) case
パターンp1, ..., pnのcase
ラベルがあり、パターンの1つpi (1≤i<n)が他のパターンpj (i<j≤n)よりも優先される場合、コンパイル時にエラーが発生します。
次のいずれかに該当する場合は、コンパイル時にエラーが発生します:
switchブロックに、
case
パターンを持つcase
ラベルの前にdefault
ラベルがある。switchブロックに、
null
リテラルを持つcase
ラベルの前にdefault
ラベルがある。switchブロックに
case
null,
default
ラベルがあり、その後に他のswitchラベルが続いている。
使用する場合、
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
ラベルで文がラベル付けされており、次のいずれかの場合は、コンパイル時にエラーが発生します:
switchブロックの前述の文が正常に完了する可能性がある(14.22)、または
文に複数のswitchラベルが付けられている。
最初の条件により、文グループがパターン変数を初期化せずに別の文グループにフォール・スルーすることがなくなります。たとえば、前述の文グループから到達可能な
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について網羅的です:
switchブロックに関連付けられた
default
ラベルがある。switchブロックに関連付けられた
case
null,
default
ラベルがある。switchブロックに関連付けられている、ガードされていない
case
ラベル(総称してcase
要素)に示されるすべてのcase
定数とcase
パターンを含むセットが空ではなく、セレクタ式eの型をカバーしている。
case
要素のセットPCEは、次のいずれかの場合に型Tをカバーします:
PCEは、TとUが同じイレイジャを持つ型Uをカバーする。PCEには、Tに対して無条件(14.30.3)のパターンが含まれている。Tは上限がBの型変数であり、
PCEはBをカバーしている。Tは交差型T1
&
...&
Tnであり、PCEは型Ti (1≤ i ≤ n)のいずれかのTiをカバーしている。
- 型Tは
boolean
またはBoolean
であり、CEには定数true
と定数false
が含まれている。
型Tはenumクラス型Eであり、
PCEにはEの列挙定数のすべての名前が含まれている。default
ラベルは許容されますが、すべての列挙定数の名前がcase
定数として示される場合は必須ではありません。次に例を示します。enum E { F, G, H } static int testEnumExhaustive(E e) { return switch(e) { case F -> 0; case G -> 1; case H -> 2; // No default required! }; }
CEにはプリミティブ型Pの型パターンが含まれていて、Tはプリミティブ型Wのラッパー・クラスであり、型Wから型Pへの変換が無条件に正確(5.7.2)である。
Integer
は、型double
を網羅しています。static int doubleExhaustive(Integer i) { return switch (i) { case double p -> 0; }; }
null
はプリミティブ型のドメインにない点に注意してください。網羅的なswitchの実行は、null
が検出されるとエラーで失敗する(MatchException
がスローされる)可能性があります。
型Tは、
abstract
sealed
クラスまたはsealed
インタフェースCを指名していて、Cの許可されるすべての直接のサブクラスまたはサブインタフェースDについて、次の2つの条件のいずれかが成立する場合:Dという名前で、Tのサブタイプである型は存在しない。または
型UはDという名前のTのサブタイプであり、
PCEはUをカバーしている。
default
ラベルは許可されますが、abstract
sealed
クラスまたはsealed
インタフェースの許容された直接のサブクラスおよびサブインタフェースのすべてを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 static int testExhaustive1(I i) { return switch(i) { case A a -> 0; case B b -> 1; case C c -> 2; // No default required! }; }
switchブロックには、型
A
、B
およびC
のすべての値と一致するcase
パターンが含まれていて、それ以外の型I
のインスタンスは許可されていないため、このswitchブロックは網羅的です。許容された直接サブクラスまたはサブインタフェースは、汎用
sealed
スーパークラスまたはスーパーインタフェースの特定のパラメータ化のみを拡張できるという事実は、switchブロックが網羅的であるかどうかを判断するときには考慮が不要な場合があることを意味します。次に例を示します。sealed interface J<X> permits D, E {} final class D<Y> implements J<String> {} final class E<X> implements J<X> {} static int testExhaustive2(J<Integer> ji) { return switch(ji) { // Exhaustive! case E<Integer> e -> 42; }; }
このセレクタ式には型
J
<Integer
>があり、ji
の値がD
のインスタンスである可能性はないため、許可された直接のサブクラスD
について考慮する必要はありません。型TはレコードクラスRという名前で、
PCEにはRという名前の型を持つレコード・パターンpが含まれていて、型UのRのすべてのレコード・コンポーネント(存在する場合)について、対応するコンポーネント・パターンpを含むシングルトン・セットがUをカバーしている。対応するレコード・コンポーネントの型をすべてカバーするコンポーネント・パターンは、レコード型をカバーするとみなされます。次に例を示します。
record Test<X>(Object o, X x){} static int testExhaustiveRecordPattern(Test<String> r) { return switch(r) { // Exhaustive! case Test<String>(Object o, String s) -> 0; }; }
PCEはセットQQEに書き換え、QQEはTをカバーします。case要素のセット
PCEは、PCEのサブセットがパターンpに削減され、QQEがパターンpとともにPCEの残りの要素から構成される場合、セットQQEに書き換えられます。次のいずれかが成立する場合は、空でないパターンのセットRPが単一のパターンrpに削減されます:
RPは型Uをカバーし、rpは型Uの型パターンである。
RPは、すべての型がk (k≥1)個のコンポーネントを持つ同じレコード・クラスRに消去される型のレコード・パターンで構成されていて、他のコンポーネントci (1≤i≤k, i≠r)ごとに、コンポーネントciに対応するレコード・パターンからのコンポーネント・パターンを含んでいるセットが単一のパターンqiと等価であり、コンポーネントcrに対応するレコード・パターンからのコンポーネント・パターンを含んでいるセットが単一のパターンqに削減され、rpがパターンq1, ..., qr-1, q, qr+1, ..., qkから構成されるパターン・リストを持つ型Rのレコード・パターンであるようなRの識別されたコンポーネントcr (1≤r≤k)が存在する。
次のいずれかが成立する場合、空でないパターンのセットEPは単一のパターンepと等価です:
EPは型がすべて同じイレイジャTを持つ型パターンで構成されていて、epは型Tの型パターンである。
EPはk (k≥1)個のコンポーネントを持つ同じレコード・クラスRにすべて消去される型を持つレコード・パターンで構成されていて、それぞれのレコード・コンポーネントについては、レコード・パターンからの対応するコンポーネント・パターンを含んでいるセットが単一のパターンqj (1≤j≤k)と等価であり、epがコンポーネント・パターンq1,...qkで構成されるコンポーネント・パターン・リストを持つ型Rレコード・パターンである。
通常、レコード・パターンはレコード型の値のサブセットにのみ一致します。ただし、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 a
、B b
、C 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ラベルがセレクタ式の値に適用されるかどうかを判断する必要があります。これは次のように処理されます。
Tが参照型で、
もし値がnull参照の場合は、null
リテラルを含むcase
ラベルが適用されます。値がnull参照ではない場合は、その後値に適用されるswitchブロックの最初の(ある場合)case
ラベルを次のように決定します:Tが参照型のときに値がnull参照でない場合は、値が最初にボックス化解除変換(5.1.8)の対象になり、定数cがボックス化解除された値と等しい場合、
case
定数cのあるcase
ラベルがCharacter
、Byte
、Short
、またはInteger
、Long
、Float
、Double
またはBoolean
型の値に適用されます。ボックス化解除変換は、ボックス化解除される値がNULL参照にならないことが保証されるため、正常に完了します。
等価性は、
==
演算子で定義されます(15.21)。Tがプリミティブ型の場合、
case
定数がcのあるcase
ラベルは、char
、byte
、short
、int
、またはlong
、float
、double
、boolean
またはString
型の値または列挙型(定数cが値と等しい場合)に適用されます。等価性は、整数型およびブール型の場合は
==
演算子(15.21)および浮動小数点型の場合は表現等価性(java.lang.Double)によって定義されます。ただし値がString
の場合、その場合は等価性はクラスString
のequals
メソッドによって定義されます。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
ラベルはすべての値に適用されます。
値が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)セレクタ式の型がchar
、byte
、short
、int
、Character
、Byte
、Short
、Integer
、String
または列挙型ではない、または(ii) switchブロックに関連付けられたcase
パターンまたはnull
リテラルが存在しているもののことです。
Java SE 24より前は、
char
、byte
、short
、int
、およびswitchの有効なセレクタ型としてサポートされている参照型のみです。この制限はJava SE 24で解除されているため、セレクタ型がfloat
、double
、long
、boolean
またはそれらいずれかのラッパー型のswitch文も拡張されたものとみなされます。
switch
文のswitchブロックに対して次のすべてが当てはまる必要があり、そうでない場合はコンパイル時にエラーが発生します:
1つのみの
default
ラベルがswitch
ブロックに関連付けられています。switchブロックのすべてのswitchルール式は、文の式です(14.8)。
switch
文は、switchブロックの矢印(->
)の右にどの式が出現するか、つまり、どの式をswitchルール式として使用できるかという観点で、switch
式と異なります。switch
文では、文の式のみをswitchルール式として使用できますが、switch
式では、任意の式を使用できます(15.28.1)。switch
文が拡張switch
文の場合、それは網羅的である必要があります(14.11.1.1)。
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:
-
_
- 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は、任意の
参照型(さらに言えば、var
ではないこと)を示します。VariableDeclaratorListは単一のVariableDeclaratorで構成されています。
VariableDeclaratorにはイニシャライザがありません。
VariableDeclaratorIdにカッコのペアがありません。
最上位型パターンで宣言されたパターン変数の型は、LocalVariableTypeによって示される参照型です。
ネストされた型パターンで宣言されたパターン変数の型は、次のように決定されます。
LocalVariableTypeがUnannTypeの場合、パターン変数の型はUnannTypeで示されます
LocalVariableTypeが
var
の場合、型パターンは、レコード・パターンのコンポーネント・パターン・リストに直接示される必要があります。そうしないと、コンパイル時にエラーが発生します。Rをレコード・パターンの型とし、TをRの対応するコンポーネント・フィールドの型(8.10.3)とします。パターン変数の型はTで指定しているすべての合成型変数に関するTの上方投影です。
次のレコード・クラスの宣言について考えます:
record R<T>(ArrayList<T> a){}
レコード・パターン
R
<String
>(var b)
の場合、パターン変数b
の型はArrayList
<String
>です。
型パターンは、型Rのレコード・パターンのコンポーネント・パターン・リストに直接示される場合、null一致と呼ばれます。ここで、Rの対応するレコード・コンポーネントは型U、Uは参照型であり、型パターンは型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の型とし、TをRの対応するコンポーネント・フィールドの型(8.10.3)とします。match-allパターンの型は、Tで指定しているすべての合成型変数に関するTの上方投影です。
match-allパターンは、名前なしパターン変数を宣言し、LocalVariableTypeが
var
であるネストした型パターンと同等であることがわかります。
14.30.2 パターン・マッチング
パターン・マッチングは、実行時にパターンに対して値をテストするプロセスです。パターン・マッチングは、文実行(14.1)および式評価(15.1)とは異なります。値がパターンに正常に一致する場合、パターン・マッチング・プロセスが、パターンによって宣言されたすべてのパターン変数(ある場合)を初期化します。
パターン・マッチングのプロセスには、式の評価または文の実行が関与する場合があります。したがって、式の評価または文の実行が突然完了する場合は、パターン・マッチングが突然完了すると表現されます。突然の完了には常に理由が関連付けられており、それは常に、指定された値を持つthrow
です。パターン・マッチングは、突然完了しない場合、正常に完了すると言い表されます。
値がパターンと一致するかどうかを決定するルールおよびパターン変数を初期化するルールは、次のとおりです。
型パターンがnull一致の場合(14.30.1)、null参照は型パターンに一致し、それ以外の場合は一致しません。
null参照が一致する場合は、型パターンによって宣言されたパターン変数がnull参照に初期化されます。
null参照が一致しない場合、型パターンによって宣言されたパターン変数は初期化されません。
null参照でない値vは、vがターゲット型Tに対して正確(5.7.1)であるいずれかのテスト変換
(5.7)によって変換ClassCastException
の発生なしに可能なできる場合に、型Tの型パターンと一致します。それ以外の場合は、一致しません。vが一致する場合、型パターンによって宣言されたパターン変数はvに初期化されます。
vが一致しない場合、型パターンによって宣言されたパターン変数は初期化されません。
null参照は、レコード・パターンと一致しません。
この場合、レコード・パターンに含まれる宣言に示される任意のパターン変数は初期化されません。
null参照でない値vは、(i) vが、
、正確な(5.7.1)テスト変換ClassCastException
を発生させることなく(5.7)によってターゲット型Rに変換できる場合と、(ii) vの各レコード・コンポーネントがLの対応するコンポーネント・パターンと一致する場合に、型Rおよびコンポーネント・パターン・リストLのレコード・パターンに一致し、それ以外の場合は一致しません。vの各レコード・コンポーネントは、そのコンポーネントに対応するvのアクセサ・メソッドを呼び出すことによって決定されます。アクセサ・メソッドの呼出しの実行が理由Sのために突然完了した場合、パターン・マッチングは原因Sによる
MatchException
をスローすることで突然完了します。レコード・パターンのコンポーネント・パターン・リストに示されるパターンで宣言されたパターン変数は、リスト内のすべてのパターンが一致する場合にのみ初期化されます。
すべての値はmatch-allパターンと一致します。
14.30.3 パターンのプロパティ
次のいずれかの規則が適用される場合、パターンpは型Tで適用可能であると表現されます。
型Tから型Uへのテスト変換(5.7)がある場合、
参照型Uのパターン変数を宣言する型パターンは、参照型Tに適用できます。プリミティブ型Pのパターン変数を宣言する型パターンは、型Pで適用可能です。型Rとパターン・リストLのレコード・パターンは、(i)型Tから型Rへのテスト変換(5.7)があり、(ii) Lに現れるすべてのコンポーネント・パターンp (ある場合)について、pがRの対応するコンポーネント・フィールドの型で適用可能な場合に、型Tで適用できます。
match-allパターンは、すべての型Tに適用できます。
パターンpは、型Tのすべての値がpと一致することがある一致する場合に型Tに対して無条件と表現されます。そのため、パターン・マッチングのテストの側面は排除できますそのため、実行時のパターン・マッチングに処置は不要になります。次のように定義されます:
参照型Sのパターン変数を宣言する型パターンはTのイレイジャがSのイレイジャのサブタイプである|T|から|S|への無条件に正確(5.7.2)なテスト変換が存在する場合に、参照型Tに対して無条件です。
- プリミティブ型Pのパターン変数を宣言する型パターンは、型Pに対して無条件です。
- match-allパターンは、すべての型Tに対して無条件です。
null参照はレコード・パターンと一致しないため、無条件であるレコード・パターンはありません。
qと一致するすべての値がpとも一致する場合、パターンpは別のパターンqよりも優先されると表現され、次のように定義されます。
パターンpは、pがTに対して無条件の場合、型Tのパターン変数を宣言する型パターンよりも優先されます。
パターンpは、pがRに対して無条件である場合、型Rのレコード・パターンよりも優先されます。
(i) RとSが同じレコード・クラスを指定し、(ii) Lの各コンポーネント・パターンが対応するMのコンポーネント・パターンを優先する場合、型Rでパターン・リストLのレコード・パターンは、型Sでパターン・リストMの別のレコード・パターンよりも優先されます。
パターンpは、pがTに対して無条件である場合、型Tのすべての一致するパターンよりも優先されます。
第15章: 式
15.5 式と実行時のチェック
式の型がプリミティブ型の場合、式の値は同じプリミティブ型になります。
式の型が参照型の場合、参照されるオブジェクトのクラスや、その値がnull
ではなくオブジェクトへの参照であるかどうかは、コンパイル時にはわからないことがあります。
Javaプログラミング言語には、参照されるオブジェクトの実際のクラスまたはプリミティブ型の値が、式の型からは推論できない方法でプログラムの実行に影響を与える場所がいくつかあります。パラメータは次のとおりです。
メソッドの呼出し(15.12)。呼出し
o.m(...)
に使用される特定のメソッドは、型がo
のクラスまたはインタフェースの一部であるメソッドに基づいて選択されます。インスタンス・メソッドの場合、サブクラスが親クラスですでに宣言されている特定のメソッドをオーバーライドして、このオーバーライド・メソッドが呼び出されることがあるため、o
の実行時の値によって参照されるオブジェクトのクラスが加わります。(オーバーライド・メソッドは、元のオーバーライドされたm
メソッドをさらに呼び出すかどうかを選択できます。)instanceof
演算子(15.20.2)。型が参照型の式は、a)式の実行時の値で参照されるクラスまたはb)式の値のプリミティブ型が、別の参照型に変換される可能性があるかどうかを調べるためにinstanceof
を使用してテストできます。キャスト(15.16)。オペランド式の実行時の値によって参照されるオブジェクトのクラスは、キャスト演算子で指定された型と互換性がないことがあります。参照型の場合、実行時に決定された参照オブジェクトのクラスをターゲット型に変換できない場合に、例外をスローする実行時のチェックが必要になることがあります。
参照型(10.5、15.13、15.26.1)の配列コンポーネントへの割当て。型チェック・ルールでは、SがTのサブタイプである場合に、配列型S
[]
をT[]
のサブタイプとして処理できますが、これには、キャストについて実行されるチェックと同様に、配列コンポーネントへの割当てについて実行時のチェックが必要です。例外処理(14.20)。例外は、スローされた例外オブジェクトのクラスが
catch
句の仮パラメータの型であるinstanceof
の場合にのみcatch
句で捕捉されます。
オブジェクトのクラスが静的に認識されていない状況では、実行時の型エラーが発生する可能性があります。
また、実行時に静的に認識されいる型が正確でない場合もあります。そのような状況は、コンパイル時に無検査の警告が発生するプログラムで発生する可能性があります。このような警告は、静的に安全が保証できない操作と、非具象化可能型(4.7)が含まれるためにすぐに動的チェックの対象にできない操作に応じて提示されます。その結果として、プログラムの実行の後半で動的チェックを行うと、不整合が検出されて実行時の型エラーが発生する可能性があります。
実行時の型エラーは、次の状況でのみ発生する可能性があります:
オペランド式の値によって参照されるオブジェクトの実際のクラスが、キャスト演算子(5.5、15.16)で指定されたターゲット型と互換性がないときのキャスト。この場合は、
ClassCastException
がスローされます。非具象化可能型(4.7)に対する操作の妥当性を保証するために導入される、自動的に生成されたキャスト。
割り当てられる値によって参照されるオブジェクトの実際のクラスが、配列の実際の実行時コンポーネント型(10.5、15.13、15.26.1)と互換性がない場合の参照型の配列コンポーネントへの割当て。この場合は、
ArrayStoreException
がスローされます。try
文(14.20)のcatch
句で例外が捕捉されないとき。この場合は、例外を検出した制御スレッドは、最初に捕捉されない例外のハンドラ(11.3)を呼び出して終了します。
15.16 Cast式
キャスト式は、実行時にある数値型の値を別の数値型の同様の値に変換します。または、コンパイル時に式の型がboolean
であることを確認します。または、実行時に参照値が指定された参照型または参照型のリストと互換性のあるクラスを持つオブジェクトを指しているか、プリミティブ型の値を実装するオブジェクトを指しているかをチェックします。
- CastExpression:
-
(
PrimitiveType)
UnaryExpression -
(
ReferenceType {AdditionalBound})
UnaryExpressionNotPlusMinus -
(
ReferenceType {AdditionalBound})
LambdaExpression #
便宜上、ここでは[4.4]の次のプロダクションを示します:
- AdditionalBound:
&
InterfaceType
括弧と括弧内に含まれる型または型のリストは、キャスト演算子と呼ばれることもあります。
キャスト演算子に型のリストが含まれている(つまり、ReferenceTypeの後に1つ以上のAdditionalBound項が続く)場合は、次のすべてに当てはまる必要があります。それ以外の場合は、コンパイル時にエラーが発生します:
ReferenceTypeは、クラスまたはインタフェース型を示している必要があります。
リストされているすべての型のイレイジャ(4.6)は、ペア単位で異なっている必要があります。
リストされた2つの型は、同じ汎用インタフェースの異なるパラメータ化のサブタイプにすることはできません。
キャスト式によって導入されたキャスト・コンテキストのターゲット型(5.5)は、キャスト演算子に示されるPrimitiveTypeまたはReferenceType (その後にAdditionalBound項が続いていない場合)か、キャスト演算子に示されるReferenceTypeとAdditionalBound項が表す交差型です。
キャスト式の型は、このターゲット型に取得変換(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
が型比較演算子の場合に適用されます。
式RelationalExpressionの型は、参照型またはnull型である必要があり、そうでない場合、コンパイル時にエラーが発生します。RelationalExpressionの型は、
ReferenceTypeとのキャスト互換可能性をチェックする必要があります(5.5)型へのテスト変換で変換可能になる必要があります(5.7)。それ以外の場合は、コンパイル時にエラーが発生します。実行時に型比較演算子の結果は次のように決定されます。
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
がパターン一致演算子の場合に適用されます。
式RelationalExpressionの型は、参照型またはnull型である必要があり、そうでない場合、コンパイル時にエラーが発生します。パターンは、式RelationalExpressionの型(14.30.3)で適用可能である必要があります。それ以外の場合は、コンパイル時にエラーが発生します。
実行時に、パターン一致演算子の結果は次のように決定されます:
Typeが参照型でRelationalExpressionの値がnull参照の場合、結果は
false
になります。Typeが参照型で、RelationalExpressionの値がNULL参照でないとき、またはTypeがプリミティブ型のとき、値がPattern (14.30.2)と一致する場合、結果は
true
になり、それ以外の場合はfalse
になります。true
の結果の悪影響は、パターンで宣言されたすべてのパターン変数(ある場合)が初期化されることです。
例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
式も正しくありません。一方、クラスPoint
がElement
のサブクラスであった場合(この例では明らかに奇妙な表記です):
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はセレクタ式と呼ばれます。セレクタ式の型は、。それ以外の場合は、どの型でもコンパイル時にエラーが発生します。char
、byte
、short
、int
または参照型である必要があります
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