目次
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)では、式の値がパターンによって明示的に指定された型に変換されます(§14.30)。 テスト・コンテキストは、割当てコンテキストや緩い呼出しコンテキストよりも包括的ですが、キャスト・コンテキストほど包括的ではありません。
「変換」という用語は、特定のコンテキストで許可される変換を具体的にせずに記述するためにも使用されます。 たとえば、ローカル変数のイニシャライザである式が「割当て変換」の対象であると表現した場合は、割当てコンテキストのルールに従って、特定の変換がその式に暗黙的に選択されることを意味します。 別の例として、式がキャスト変換の対象となり、式の値がキャスト・コンテキストで許可されたとおりに変換されることを意味します。
例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
Javaプログラミング言語の特定の型変換は、12種類に分けられます。
タイプから同じタイプへの変換は、どのタイプでも許可されます。
これは些細なことのように思えるかもしれませんが、2つの実用的な結果があります。 まず、式が目的のタイプで始まることを常に許可されるため、簡単なアイデンティティ変換のみの場合、すべての式が変換の対象となるという単純に明記されたルールが可能になります。 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
したがって、int型からfloat型への変換中に情報が失われたことを示します。これは、float型の値が9桁まで正確でないためです。
プリミティブ型に対する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への絞込み変換は、2進浮動小数点形式間の変換で、丸め処理ポリシー(§15.4)を使用して、IEEE 754のルールによって決定されたとおりに行われます。 この変換では精度は失われますが、範囲も失われるため、ゼロ以外のdoubleからのfloatゼロと、有限doubleからのfloat無限大になります。 double NaNはfloat NaNに変換され、double無限大は同じ符号付きfloat無限大に変換されます。
オーバーフロー、アンダーフロー、またはその他の情報の損失が発生する可能性があるという事実にもかかわらず、狭いプリミティブ変換は実行時例外にはならない(§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ビットであるバイト結果、つまり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
次の変換は、プリミティブ変換の拡大と縮小の両方を組み合わせたものです。
byteからchar
まず、byteは、拡大プリミティブ変換(§5.1.2)によってintに変換され、その後、結果のintは、プリミティブ変換(§5.1.3)を絞り込むことによってcharに変換されます。
拡大参照変換は、SがTのサブタイプ(§4.10)である場合、任意の参照タイプSから任意の参照タイプTに存在します。
参照変換の拡大は、実行時に特別なアクションを必要としないため、実行時に例外をスローすることはありません。 これらは、単に参照に関して、コンパイル時に正しいことが証明できる方法で他の型を持つことから構成されます。
null型は参照型ではないため(§4.1)、null型から参照型への拡張参照変換は存在しません。 ただし、多くの変換コンテキストでは、null型を明示的に参照型に変換できます。
絞込み参照変換は、参照型Sの式を異なる参照型Tの式として扱います。ここで、SはTのサブタイプではありません。 サポートされている型のペアは、§5.1.6.1で定義されています。 参照変換の拡大とは異なり、型は直接関連している必要はありません。 ただし、どちらの型の値も存在しないことを静的に証明できる場合、特定の型のペア間の変換が禁止される制限があります。
ナロー参照変換では、S型の値がT型の正当な値であることを検証するために、実行時にテストが必要になる場合があります。 ただし、実行時にパラメータ化された型情報が不足しているため、一部の変換はランタイム・テストで完全に検証できず、コンパイル時にフラグが付けられます(§5.1.6.2)。 ランタイム・テストで完全に検証できる変換、およびパラメータ化された型情報を含むが、実行時に部分的に検証できる特定の変換については、テストが失敗するとClassCastExceptionがスローされます(§5.1.6.3)。
次のことがすべて当てはまる場合、参照型Sから参照型Tへの絞り込み参照変換が存在します。
Sは Tのサブタイプではありません(§4.10)
Tのスーパータイプであるパラメータ化された型Xが存在し、XとYの消去が同じになるように、Sのスーパータイプであるパラメータ化された型Yが存在する場合、XとYは明らかに区別されません(§4.5)。
java.utilパッケージの型を例として使用すると、型引数StringとObjectが明確に異なるため、ArrayList<String>からArrayList<Object>への絞込み参照変換(またはその逆)は存在しません。 同じ理由で、ArrayList<String>からList<Object>への絞込み参照変換は存在せず、その逆も存在します。 明確に異なる型を拒否することは、無意味な絞り込み参照変換を防ぐ簡単な静的ゲートとなります。
次のケースのいずれかが適用されます。
Sはクラスまたはインタフェース型で、Tはクラスまたはインタフェース型で、Sは Tで指定されたクラスまたはインタフェースとは無関係なクラスまたはインタフェースを指定します(「無関係」は以下で定義します)。
Sはクラス型Object、インタフェース型java.io.SerializableまたはCloneable(配列によって実装される唯一のインタフェース(§10.8))、Tは配列型です。
Sは配列型 SC[]、つまり SC型のコンポーネントの配列です。Tは配列型 TC[]、つまり TC型のコンポーネントの配列であり、SCから TCへの狭い参照変換が存在します。
Sは型変数であり、Sの上限から Tへの狭い参照変換が存在します。
Tは型変数で、Sから Tの上限までの拡大参照変換または縮小参照変換のいずれかが存在します。
Sは交差型 S1 & ... & Snで、すべての i (1 ≤ i ≤ n)に対して、拡大参照変換または縮小参照変換が Siから Tに存在します。
Tは交差型 T1 & ... & Tnで、すべての i (1 ≤ i ≤ n)に対して、拡大参照変換または縮小参照変換が Sから Tiに存在します。
クラスまたはインタフェースは、共通インスタンス(null値以外)がないことを静的に判断できる場合に、別のクラスまたはインタフェースから非結合です。 不一致のルールは次のとおりです。
(i) C <: Iが C <: Iでない場合、および(ii)次のいずれかのケースが当てはまる場合、Cという名前のクラスは Iという名前のインタフェースとは無関係です。
Cはfinalです。
Cはsealedで、Cで許可されるすべての直接サブクラスはIとは無関係です。
Cは自由に拡張可能(§8.1.1.2)で、Iは sealedで、Cは Iの許可されたすべての直接サブクラスおよびサブインタフェースとは無関係です。
Iという名前のインタフェースは、Cが Iと無関係である場合、Cという名前のクラスとは無関係です。
(i) C <: Dが C <: Dでなく、(ii) D <: Cがそうでない場合、Cという名前のクラスは Dという名前のクラスとは無関係です。
(i) I <: Jが I <: Jでない場合、および(ii) J <: Iが当てはまらない場合、および(iii)次のいずれかのケースが当てはまる場合、Iという名前のインタフェースは別のインタフェースとは無関係です。
Iはsealedで、Iで許可されるすべての直接サブクラスおよびサブインタフェースはJとは無関係です。
Jはsealedで、IはJで許可されるすべての直接サブクラスおよびサブインタフェースとは無関係です。
クラスがfinalかどうかは、そのクラスがインタフェースと非結合であるかどうかに最も影響します。 次の宣言について考えてみます:
interface I {}
final class C {}
クラス Cは finalであり、Iを実装しないため、Iのインスタンスでもある Cのインスタンスは存在できないため、Cと Iは無関係です。 したがって、Cから Iへの限定参照変換はありません。
対照的に、次の宣言を考えてみましょう。
interface J {}
class D {}
クラス Dが Jを実装していない場合でも、Dのインスタンスが Jのインスタンスになることは可能です。たとえば、次の宣言が発生した場合です。
class E extends D implements J {}
このため、DはJと無関係ではなく、DからJへの絞込み参照変換があります。
前述の最後の句は、2つの自由に拡張可能なインタフェース(§9.1.1.4)がばらばらではないことを意味します。
ナロー化参照変換は、チェック済または未チェックのいずれかです。 これらの用語は、変換のタイプが正確かどうかを検証するJava Virtual Machineの機能を示します。
絞り込み参照変換の選択が解除されている場合、Java Virtual Machineはその型の正確性を完全に検証できず、ヒープ汚染につながる可能性があります(§4.12.2)。 これをプログラマにフラグ設定するには、@SuppressWarnings (§9.6.4.5)によって抑制されないかぎり、選択されていない絞込み参照変換によってコンパイル時の未チェックの警告が発生します。 逆に、絞込み参照変換のチェックが外れていない場合はチェックされます。Java Virtual Machineは、その型の正確性を完全に検証できるため、コンパイル時に警告は表示されません。
選択されていない絞込み参照変換は次のとおりです。
型 Sからパラメータ化されたクラスまたはインタフェース型 Tへの狭い参照変換は、次のうち少なくとも1つに該当しないかぎり、選択解除されます。
Tのすべての型引数は、バインドされていないワイルドカードです。
T <: Sおよび Sには、Xの型引数が Tの型引数に含まれていない T以外のサブタイプ Xがありません。
型 Sから型変数 Tへの絞り込み参照変換は選択解除されます。
型 Sから交差型 T1 & ... & Tnへの狭義参照変換は、Ti (1 ≤ i ≤ n)が Sが Tiのサブタイプではなく、Sから Tiへの狭義参照変換の選択が解除される場合に選択解除されます。
チェックされた参照変換はすべて、実行時に妥当性チェックが必要です。 主に、これらの変換は、パラメータ化されていないクラスおよびインタフェース型に対して行われます。
選択解除された参照変換の中には、実行時に妥当性チェックが必要なものがあります。 これは、選択されていない絞込み参照変換が「完全に選択解除済」か「部分的に選択解除済」かによって異なります。 部分的に選択されていない絞込み参照変換では、実行時に妥当性チェックが必要ですが、完全に選択されていない絞込み参照変換では有効性チェックは行われません。
これらの用語は、RAW型として表示された場合、変換に関与する型の互換性を示します。 変換が概念的に「アップキャスト」である場合、変換は完全に選択解除されます。Java Virtual Machineの非汎用型システムでは変換が合法であるため、実行時テストは不要です。 逆に、変換が概念的に「ダウンキャスト」である場合、変換は部分的に選択解除されます。Java Virtual Machineの非汎用型システムでも、変換に関与する(raw)タイプの互換性をテストするためにランタイム・チェックが必要です。
例としてjava.utilパッケージの型を使用すると、(raw)型ArrayListはJava Virtual Machineの(raw)型Collectionのサブタイプであるため、ArrayList<String>からCollection<T>への変換は完全に選択解除されます。 逆に、Collection<T>からArrayList<String>への変換は、(RAW)タイプCollectionがJava Virtual Machineの(RAW)タイプArrayListのサブタイプではないため、部分的にチェックされません。
選択されていない絞込み参照変換のカテゴリ化は次のとおりです。
|S| <: |T|の場合、Sから非交差型Tへの選択されていない絞込み参照変換は完全に選択解除されます。
それ以外の場合は、部分的に選択解除されます。
すべての i (1 ≤ i ≤ n)について、S <: Tiまたは Sから Tiへの狭義参照変換のいずれかが、S <: Tiから Tiまで選択されていない場合、選択されていない狭義参照変換は完全に選択解除されます。
それ以外の場合は、部分的に選択解除されます。
チェック済または一部未チェックの絞込み参照変換の実行時有効性チェックは、次のとおりです。
実行時の値がnullの場合、変換が許可されます。
それ以外の場合は、Rを値によって参照されるオブジェクトのクラスにし、Tを変換先の型の消去(§4.6)にしてください。 次に、
Rが通常のクラス(配列クラスではない)の場合:
Rがインタフェースの場合:
これらのルールが特定の変換に初めて適用される場合、Rはインタフェースにはできませんが、実行時参照値は要素タイプがインタフェース・タイプの配列を参照できるため、ルールが再帰的に適用される場合、Rはインタフェースになることがあります。
Tがクラス型の場合、TはObject (§4.3.2)である必要があります。そうでない場合は、ClassCastExceptionがスローされます。
Tがインタフェース・タイプの場合、RはTと同じインタフェースであるか、Tのサブインタフェースであるか、またはClassCastExceptionがスローされます。
Tが配列型の場合は、ClassCastExceptionがスローされます。
Rが配列型RC[]を表すクラス、つまりRC型のコンポーネントの配列の場合:
Tがクラス型の場合、TはObject (§4.3.2)である必要があります。そうでない場合は、ClassCastExceptionがスローされます。
Tがインタフェース型の場合、Tはjava.io.Serializable型またはCloneable型(配列によって実装される唯一のインタフェース)である必要があります。そうでない場合は、ClassCastExceptionがスローされます。
Tが配列型TC[]、つまりTC型のコンポーネントの配列の場合、TCとRCのいずれかが同じプリミティブ型であるか、TCとRCが参照型であり、これらのランタイム・ルールの再帰的アプリケーションによって許可されていないかぎり、ClassCastExceptionがスローされます。
変換が交差タイプ T1 & ... & Tnである場合、すべての i (1 ≤ i ≤ n)に対して、交差タイプへの変換に Sから Tiへの変換に必要な実行時検査も必要です。
ボクシング変換では、プリミティブ型の式が対応する参照型の式として処理されます。 具体的には、次の9つの変換をボックス化変換と呼びます。
タイプbooleanからタイプBooleanまで
タイプbyteからタイプByteまで
タイプshortからタイプShortまで
タイプcharからタイプCharacterまで
タイプintからタイプIntegerまで
タイプlongからタイプLongまで
タイプfloatからタイプFloatまで
タイプdoubleからタイプDoubleまで
null型からnull型へ
条件演算子(§15.25)はオペランドの型にボクシング変換を適用し、その結果をさらに計算するので、この規則が必要である。
実行時には、ボクシング変換は次のように進みます。
pがboolean型の値である場合、ボクシング変換はpをのようにr.booleanValue() == pBoolean型のクラスおよび参照rに変換します。
pがbyte型の値である場合、ボクシング変換はpをのようにr.byteValue() == pByte型のクラスおよび参照rに変換します。
pがchar型の値である場合、ボクシング変換はpをのようにr.charValue() == pCharacter型のクラスおよび参照rに変換します。
pがshort型の値である場合、ボクシング変換はpをのようにr.shortValue() == pShort型のクラスおよび参照rに変換します。
pがint型の値である場合、ボクシング変換はpをのようにr.intValue() == pInteger型のクラスおよび参照rに変換します。
pがlong型の値である場合、ボクシング変換はpをのようにr.longValue() == pLong型のクラスおよび参照rに変換します。
pがfloat型の値の場合:
pがNaNでない場合、ボクシング変換はpをクラスおよびFloat型の参照rに変換し、がr.floatValue()pに評価されるようにします。
それ以外の場合、ボクシング変換はpをクラスおよびタイプFloatの参照rに変換して、がr.isNaN()trueに評価されるようにします。
pがdouble型の値の場合、次のようになります。
pがNaNでない場合、ボクシング変換はpをクラスおよびDouble型の参照rに変換し、がr.doubleValue()pに評価されるようにします。
それ以外の場合、ボクシング変換はpをクラスおよびタイプDoubleの参照rに変換して、がr.isNaN()trueに評価されるようにします。
pが他の型の値である場合、ボクシング変換はアイデンティティ変換(§5.1.1)と同等です。
ボクシングされる値pが、boolean、byte、char、short、intまたはlong型の定数式(§15.29)を評価した結果であり、結果がtrue、false、'\\u0000'から'\\u007f'までの範囲の文字、または-128から127までの範囲の整数である場合、aおよびbをpの2つのボクシング変換の結果にしてください。 a == bは常にそうです。
プリミティブ値をボクシングすると、常に同じ参照が生成されます。 実際には、これは既存の実装手法では実現できません。 前述のルールは実用的な妥協であり、特定の共通値を常に識別できないオブジェクトにボックス化する必要があります。 実装では、これらを遅延または即時キャッシュできます。 他の値の場合、ルールはプログラマ側のボックス化された値の識別に関する仮定を禁止します。 これにより、これらの参照の一部またはすべてを共有できます(ただし、必須ではありません)。
これにより、ほとんどの一般的なケースでは、特に小規模なデバイスで過度のパフォーマンス・ペナルティを課すことなく、動作が望ましい動作になることが保証されます。 メモリー制限が少ない実装では、たとえば、すべてのcharおよびshort値、および-32Kから+32Kの範囲のintおよびlong値をキャッシュできます。
いずれかのラッパー・クラス(Boolean、Byte、Character、Short、Integer、Long、FloatまたはDouble)の新規インスタンスを割り当てる必要があり、使用可能な記憶域が不足している場合、ボクシング変換によってOutOfMemoryErrorが発生する可能性があります。
ボックス化解除変換では、参照型の式が対応するプリミティブ型の式として扱われます。 具体的には、次の8つの変換をアンボックス化変換と呼びます。
タイプBooleanからタイプbooleanまで
タイプByteからタイプbyteまで
タイプShortからタイプshortまで
タイプCharacterからタイプcharまで
タイプIntegerからタイプintまで
タイプLongからタイプlongまで
タイプFloatからタイプfloatまで
タイプDoubleからタイプdoubleまで
実行時に、ボックス化解除の変換は次のように進みます。
rがBoolean型の参照の場合、ボックス化解除変換によってrがに変換されます。r.booleanValue()
rがByte型の参照の場合、ボックス化解除変換によってrがに変換されます。r.byteValue()
rがCharacter型の参照の場合、ボックス化解除変換によってrがに変換されます。r.charValue()
rがShort型の参照の場合、ボックス化解除変換によってrがに変換されます。r.shortValue()
rがInteger型の参照の場合、ボックス化解除変換によってrがに変換されます。r.intValue()
rがLong型の参照の場合、ボックス化解除変換によってrがに変換されます。r.longValue()
rがFloat型の参照の場合、ボックス化解除変換によってrがに変換されます。r.floatValue()
rがDouble型の参照の場合、ボックス化解除変換によってrがに変換されます。r.doubleValue()
rがnullの場合、ボックス化解除変換によってNullPointerExceptionがスローされます。
型が数値型(§4.2)である場合、または型がボックス化解除変換によって数値型に変換される参照型である場合、型は数値型に変換可能であるとみなされます。
型は、整数型に変換可能(整数型の場合)またはアンボクシング変換によって整数型に変換される参照型であるとみなされます。
Gは、n型パラメータを使用して汎用型宣言に名前を付けます。
RAWクラスまたはインタフェース・タイプ(§4.8)Gから、G<T1、...、Tn>という形式のパラメータ化された型への未チェック変換があります。
RAW配列型G[]kからG<T1、...、Tn>[]k形式の任意の配列型への未チェック変換があります。 (表記[]kは、kディメンションの配列タイプを示します。)
チェックされていない変換を使用すると、すべての型引数 Ti (1 ≤ i ≤ n)がバインドされていないワイルドカード(§4.5.1)である場合、または警告が @SuppressWarnings (§9.6.4.5)によって抑制されている場合を除き、コンパイル時の unchecked警告が発生します。
未チェックの変換は、汎用タイプの導入前に記述されたレガシー・コードの円滑な相互運用を可能にするために使用され、汎用性(生成と呼ばれるプロセス)を使用する変換が行われたライブラリを使用します。 このような状況(特に、java.utilのCollections Frameworkのクライアント)では、レガシー・コードがRAW型(たとえば、Collection<String>ではなくCollection)を使用します。 raw型の式は、対応する仮パラメータの型と同じ型のパラメータ化されたバージョンを使用するライブラリ・メソッドに引数として渡されます。
このような呼び出しは、ジェネリックスを使用する型システムでは静的に安全であると示すことはできません。 このような呼び出しを拒否すると、既存のコードの大規模なボディが無効になり、ライブラリの新しいバージョンを使用できなくなります。 これにより、ライブラリ・ベンダーが汎用性を利用できなくなる可能性があります。 このような不快なイベント発生を防ぐために、raw型はraw型が参照する汎用型宣言の任意の呼び出しに変換できます。 変換は健全ではありませんが、実用性への譲歩として許容されます。 このような場合は、チェックされていない警告が発行されます。
Gは、§8.1.2、 §9.1.2)に n型パラメータ A1、...、Anと対応する境界 U1、...、Unを付けて汎用型宣言(§8.1.2、 §9.1.2)という名前を付けます。
パラメータ化された型G<T1、...、Tn> (§4.5)からパラメータ化された型G<S1、...、Sn>への≤ i ≤ nへのn>の取得変換が存在する。
Tiが?形式のワイルドカード型引数(§4.5.1)である場合、Siは、上限がUi[A1:=S1,...,An:=Sn]で、下限がnull型(§4.1)である新しい型変数です。
Tiが? extends Bi形式のワイルドカード型引数である場合、Siは、上限がglb(Bi、Ui[A1:=S1,...,An:=Sn])で、下限がnull型である新しい型変数です。
glb(V1、...、Vm)は、 V1 & ... & Vmとして定義されています。
ViおよびVjの2つのクラス(インタフェースではない)で、ViがVjのサブクラスでない場合、またはその逆の場合、コンパイル時にエラーが発生します。
Tiが? super Bi形式のワイルドカード型引数である場合、Siは、上限がUi[A1:=S1,...,An:=Sn]で下限がBiである新しい型変数です。
それ以外の場合は、Si = Tiです。
パラメータ化された型(§4.5)以外の型でのキャプチャ変換は、アイデンティティ変換(§5.1.1)として機能します。
取得変換は再帰的に適用されません。
取得変換では、実行時に特別なアクションを必要としないため、実行時に例外がスローされることはありません。
キャプチャ変換は、ワイルドカードをより便利にするように設計されています。 動機を理解するには、まずメソッドjava.util.Collections.reverse()を確認します。
public static void reverse(List<?> list);
このメソッドは、パラメータとして指定されたリストを元に戻します。 これは任意のタイプのリストに対して機能するため、ワイルドカード型List<?>を仮パラメータの型として使用することは完全に適切です。
次に、reverse()の実装方法を検討します。
public static void reverse(List<?> list) { rev(list); }
private static <T> void rev(List<T> list) {
List<T> tmp = new ArrayList<T>(list);
for (int i = 0; i < list.size(); i++) {
list.set(i, tmp.get(list.size() - i - 1));
}
}
実装では、リストをコピーし、コピーから要素を抽出して、元の値に挿入する必要があります。 これをタイプセーフな方法で実行するには、受信リストの要素タイプにTという名前を付ける必要があります。 これは、プライベート・サービス・メソッドrev()で行います。 そのためには、List<?>型の受信引数リストをrev()の引数として渡す必要があります。 一般に、List<?>は不明な型のリストです。 どのタイプTでも、List<T>のサブタイプではありません。 そのようなサブタイプ関係を許可することは、意味がありません。 次の方法を確認してください。
public static <T> void fill(List<T> l, T obj)
次のコードは、型システムを損なうことになります。
List<String> ls = new ArrayList<String>();
List<?> l = ls;
Collections.fill(l, new Object()); // not legal - but assume it was!
String s = ls.get(0); // ClassCastException - ls contains
// Objects, not Strings.
そのため、特別な交付がないと、reverse()からrev()へのコールが許可されないことがわかります。 その場合、reverse()の作成者は、次のように署名の書込みを強制されます。
public static <T> void reverse(List<T> list)
これは、実装情報をコール元に公開するため、望ましくありません。 さらに悪いことに、APIの設計者は、ワイルドカードを使用した署名がAPIの呼出し側に必要なものであると判断し、後でタイプ・セーフ実装が除外されたことに気付くだけです。
reverse()からrev()へのコールは実際には無害ですが、List<?>とList<T>の一般的なサブタイピング関係に基づいて正当化することはできません。 呼出しは無害です。これは、着信引数が疑わしい(未知の)タイプのリストであるためです。 型変数Xでこの不明な型を取得できる場合は、TをXと推測できます。 これが捕獲変換の本質です。 もちろん、この仕様は、非簡易(および場合によっては再帰的に定義される)上限または下限、複数の引数の存在などの合併症に対処する必要があります。
数学的に洗練された読者は、キャプチャ変換を確立された型理論に関連付けることを望みます。 型理論に精通していない読者は、この議論を省略するか、Benjamin PierceによるTypes and Programming Languagesなどの適切なテキストを調査してから、このセクションを再検討してください。
次に、確立された型理論概念への取得変換の関係の簡単な概要を示します。 ワイルドカード・タイプは、存在タイプの制限された形式です。 キャプチャ変換は、存在型の値の開口部に緩く対応します。 式eの取得変換は、eを囲む最上位レベルの式を構成するスコープ内のeのopenと考えることができます。
既存に対する従来のopen操作では、取得された型変数がオープンされた式をエスケープしてはいけません。 取得変換に対応するopenは、取得された型変数をそのスコープ外に表示できないほど十分な大きさのスコープに常にあります。 このスキームの利点は、第16回欧州オブジェクト指向プログラミング会議(ECOOP 2002)の進行において、パラメトリック・タイプの分散ベースのサブタイピングに関する論文(Atsushi IgarashiおよびMirko Viroli)で定義されているように、close操作が不要であることです。 ワイルドカードの正式な説明については、Mads Torgersen、Erik Ernst、Christian Plesner HansenによるWild FJ(オブジェクト指向プログラミングの基礎に関する第12回ワークショップ)を参照してください(FOOL 2005)。
文字列変換によって、任意の型をString型に変換できます。
プリミティブ型Tの値xは、まず、適切なメソッド呼出しの引数として渡すかのように参照値に変換されます。
Tがbooleanの場合は、Boolean.valueOf(を使用します。
x)
Tがcharの場合は、Character.valueOf(を使用します。
x)
Tがbyte、shortまたはintの場合は、Integer.valueOf(を使用します。
x)
Tがlongの場合は、Long.valueOf(を使用します。
x)
Tがfloatの場合は、Float.valueOf(を使用します。
x)
Tがdoubleの場合は、Double.valueOf(を使用します。
x)
この参照値は、文字列変換によってString型に変換されます。
ここでは、参照値のみを考慮する必要があります。
参照がnullの場合は、文字列"null"に変換されます(ASCII文字は4文字、n、u、l、l)。
それ以外の場合、変換は、引数を指定しない参照オブジェクトのtoStringメソッドの呼出しのように実行されますが、toStringメソッドの呼出しの結果がnullの場合、かわりに文字列nullが使用されます。
toStringメソッドは、原始クラスObject (§4.3.2)によって定義されます。 多くのクラスが、Boolean、Character、Integer、Long、Float、DoubleおよびStringをオーバーライドします。
明示的に許可されていない変換は禁止されています。
割当てコンテキストでは、式の値を変数に代入できます(§15.26)。式の型は変数の型に変換する必要があります。
割当コンテキストでは、次のいずれかを使用できます。
前述の変換が適用された後、結果の型がRAW型(§4.8)である場合、未チェックの変換(§5.1.9)を適用できます。
さらに、式がbyte型、short型、char型またはint型の定数式(§15.29)である場合:
変数がbyte型、short型またはchar型で、定数式の値が変数の型で表現可能な場合は、絞込みプリミティブ変換を使用できます。
変数がByte型、Short型またはCharacter型で、定数式の値がbyte型、short型またはchar型でそれぞれ表現可能な場合は、絞込みプリミティブ変換の後にボクシング変換を使用できます。
定数式のコンパイル時の絞り込みは、次のようなコードを意味します。
byte theAnswer = 42;
は許可されます。 絞り込みを行わないと、整数リテラル42の型がintであるという事実は、byteへのキャストが必要であることを意味します。
byte theAnswer = (byte)42; // cast is permitted but not required
最後に、null型の値(null参照のみがそのような値)を任意の参照型に割り当てることができ、その結果、その型のnull参照になります。
変換の連鎖に、サブタイプ関係(§4.10)にない2つのパラメータ化された型が含まれている場合、コンパイル時にエラーが発生します。
このような不正なチェーンの例を次に示します。
Integer, Comparable<Integer>, Comparable, Comparable<String>
チェーンの最初の3つの要素は参照変換を拡張することで関連し、最後のエントリは未チェックの変換によって先行から導出されます。 ただし、チェーンにはサブタイプではない2つのパラメータ化された型(Comparable<Integer>およびComparable<String>)が含まれているため、これは有効な割当て変換ではありません。
代入変換によって式の型を変数の型に変換できる場合、式(またはその値)が変数に代入可能であるか、またはそれと同等に、式の型が変数の型に代入互換性があることを示します。
割当コンテキストで変換によって発生する可能性がある例外は、次のとおりです。
ClassCastExceptionは、前述の変換が適用された後、結果の値が、変数の型のイレイジャ(§4.6)のサブクラスまたはサブインタフェースのインスタンスではないオブジェクトである場合です。
この状況は、ヒープ汚染の結果としてのみ起こり得る(§4.12.2)。 実際には、フィールドの消去された型またはメソッドの消去された戻り型が、その未消去型と異なる場合に、パラメータ化された型のオブジェクトのフィールドまたはメソッドにアクセスするときにのみ、実装でキャストを実行する必要があります。
ボクシング変換の結果としてのOutOfMemoryError。
NULL参照に対するボックス化解除変換の結果としてのNullPointerException。
配列要素またはフィールド・アクセスを含む特殊な場合のArrayStoreException(§10.5、§15.26.1)。
例5.2-1. プリミティブ型の割当て
class Test {
public static void main(String[] args) {
short s = 12; // narrow 12 to short
float f = s; // widen short to float
System.out.println("f=" + f);
char c = '\u0123';
long l = c; // widen char to long
System.out.println("l=0x" + Long.toString(l,16));
f = 1.23f;
double d = f; // widen float to double
System.out.println("d=" + d);
}
}
このプログラムは出力を生成します:
f=12.0 l=0x123 d=1.2300000190734863
ただし、次のプログラムではコンパイル時エラーが発生します。
class Test {
public static void main(String[] args) {
short s = 123;
char c = s; // error: would require cast
s = c; // error: would require cast
}
}
すべてのshort値がchar値ではなく、すべてのchar値がshort値でないためです。
例5.2-2. 参照タイプの割当て
class Point { int x, y; }
class Point3D extends Point { int z; }
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) {
// Assignments to variables of class type:
Point p = new Point();
p = new Point3D();
// OK because Point3D is a subclass of Point
Point3D p3d = p;
// Error: will require a cast because a Point
// might not be a Point3D (even though it is,
// dynamically, in this example.)
// Assignments to variables of type Object:
Object o = p; // OK: any object to Object
int[] a = new int[3];
Object o2 = a; // OK: an array to Object
// Assignments to variables of interface type:
ColoredPoint cp = new ColoredPoint();
Colorable c = cp;
// OK: ColoredPoint implements Colorable
// Assignments to variables of array type:
byte[] b = new byte[4];
a = b;
// Error: these are not arrays of the same primitive type
Point3D[] p3da = new Point3D[3];
Point[] pa = p3da;
// OK: since we can assign a Point3D to a Point
p3da = pa;
// Error: (cast needed) since a Point
// can't be assigned to a Point3D
}
}
次のテスト・プログラムは、コメントで説明されているように、参照値に対する割当て変換を示しますが、コンパイルに失敗します。 この例は、前の例と比較する必要があります。
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 p = new Point();
ColoredPoint cp = new ColoredPoint();
// Okay because ColoredPoint is a subclass of Point:
p = cp;
// Okay because ColoredPoint implements Colorable:
Colorable c = cp;
// The following cause compile-time errors because
// we cannot be sure they will succeed, depending on
// the run-time type of p; a run-time check will be
// necessary for the needed narrowing conversion and
// must be indicated by including a cast:
cp = p; // p might be neither a ColoredPoint
// nor a subclass of ColoredPoint
c = p; // p might not implement Colorable
}
}
例5.2-3. 配列タイプの割当て
class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
public static void main(String[] args) {
long[] veclong = new long[100];
Object o = veclong; // okay
Long l = veclong; // compile-time error
short[] vecshort = veclong; // compile-time error
Point[] pvec = new Point[100];
ColoredPoint[] cpvec = new ColoredPoint[100];
pvec = cpvec; // okay
pvec[0] = new Point(); // okay at compile time,
// but would throw an
// exception at run time
cpvec = pvec; // compile-time error
}
}
この例では、次のようになります。
LongはObject以外のクラス型であるため、veclongの値をLong変数に割り当てることはできません。 配列は、互換性のある配列型の変数、またはObject、Cloneableまたはjava.io.Serializable型の変数にのみ割り当てることができます。
veclongの値は、プリミティブ型の配列であり、shortとlongが同じプリミティブ型ではないため、vecshortに割り当てることはできません。
cpvecの値は、pvecに割り当てることができます。これは、ColoredPoint型の式の値になる参照は、Point型の変数の値にできるためです。 その後、新しいPointをpvecのコンポーネントに割り当てると、ArrayStoreExceptionがスローされます(プログラムがコンパイルできるように修正された場合)。これは、ColoredPoint配列がコンポーネントの値としてPointのインスタンスを持てないためです。
pvecの値をcpvecに割り当てることはできません。これは、Point型の式の値である可能性のあるすべての参照が、ColoredPoint型の変数の値に正しくならないためです。 実行時のpvecの値がPoint[]のインスタンスへの参照であり、cpvecへの割当てが許可されている場合、cpvecのコンポーネントへの単純な参照(たとえば、cpvec[0])はPointを返し、PointはColoredPointではありません。 したがって、このような割り当てを許可すると、型のシステム違反が可能になります。 キャスト(§5.5、§15.16)を使用して、pvecがColoredPoint[]を参照していることを確認できます。
cpvec = (ColoredPoint[])pvec; // OK, but may throw an
// exception at run time
呼出しコンテキストでは、メソッドまたはコンストラクタ呼出し(§8.8.7.1、§15.9、§15.12)の引数値を、対応する仮パラメータに割り当てることができます。
厳密な起動コンテキストでは、次のいずれかを使用できます:
Loose呼出しコンテキストでは、厳密な呼出しコンテキストを使用して適用可能な宣言が見つからない場合にのみ、特定の呼出しに使用されるため、より許容性の高い変換セットを使用できます。 緩い起動コンテキストでは、次のいずれかを使用できます。
呼出しコンテキスト用にリストされた変換が適用された後、結果の型がRAW型(§4.8)である場合、チェックされていない変換(§5.1.9)を適用できます。
null型の値(null参照のみがそのような値)は、任意の参照型に割り当てることができます。
変換の連鎖に、サブタイプ関係(§4.10)にない2つのパラメータ化された型が含まれている場合、コンパイル時にエラーが発生します。
呼出しコンテキストで発生する可能性のある例外は、次のとおりです。
ClassCastExceptionは、前述の型変換が適用された後、結果の値が、対応する仮パラメータ型のイレイジャのサブクラスまたはサブインタフェース(§4.6)のインスタンスではないオブジェクトである場合です。
ボクシング変換の結果としてのOutOfMemoryError。
NULL参照に対するボックス化解除変換の結果としてのNullPointerException。
厳密または緩い起動コンテキストには、代入コンテキストで許可される整数定数式の暗黙的な絞り込みは含まれません。 Javaプログラミング言語の設計者は、これらの暗黙的な狭い変換を含めると、オーバーロード解決のルールに複雑さが増すと感じました(§15.12.2)。
したがって、プログラムは次のようになります。
class Test {
static int m(byte a, int b) { return a+b; }
static int m(short a, short b) { return a-b; }
public static void main(String[] args) {
System.out.println(m(12, 2)); // compile-time error
}
}
整数リテラル12および2のタイプがintであるため、オーバーロード解決のルールではメソッドmが一致しないため、コンパイル時にエラーが発生します。 整数定数式の暗黙的な絞込みを含む言語では、この例のようなケースを解決するために追加のルールが必要になります。
文字列コンテキストは、バイナリ+演算子のオペランドにのみ適用されます。これは、他のオペランドがStringの場合、Stringではありません。
これらのコンテキストのターゲット型は常にStringであり、非Stringオペランドの文字列変換(§5.1.11)は常に発生します。 +演算子の評価は、§15.18.1で指定されたとおりに進みます。
キャスティング・コンテキストを使用すると、キャスト式(§15.16)のオペランドをキャスト演算子によって明示的に指定された型に変換できます。 代入コンテキストおよび呼出しコンテキストと比較して、キャスト・コンテキストでは、§5.1で定義されたより多くの変換を使用でき、それらの変換の組合せを増やすことができます。
式がプリミティブ型の場合、キャスト・コンテキストでは次のいずれかを使用できます:
式が参照型の場合、キャスト・コンテキストでは次のいずれかを使用できます:
式にNULL型がある場合、式は任意の参照型にキャストされます。
キャスト・コンテキストで、チェックされているか部分的にチェックされていない(§5.1.6.2、§5.1.6.3)絞込み参照変換が使用される場合、式の値のクラスに対してランタイム・チェックが実行され、ClassCastExceptionが発生する可能性があります。 それ以外の場合、実行時チェックは実行されません。
選択されていない絞込み参照変換以外のキャスト変換によって式を参照型に変換できる場合、式(またはその値)が参照型と互換性のあるチェック済キャストであるとします。
参照型Sの式がチェックされ、別の参照型Tと互換性があるキャストの場合、型Sはチェックされた変換可能で型Tになります。
次の表は、特定のキャスト・コンテキストで使用される変換を列挙しています。 各変換は記号で示されます:
表では、記号間のカンマは、キャスト・コンテキストが1つの変換に続いて別の変換を使用することを示します。 型Objectは、8つのラッパー・クラスBoolean、Byte、Short、Character、Integer、Long、Float、Double以外の任意の参照型を意味します。
表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
}
}
この場合、最初のコンパイル時エラーが発生するのは、クラス型LongとPointが無関係(つまり、それらは同じではなく、どちらも他方のサブクラスでもない)であるためです。そのため、これらの間のキャストは常に失敗します。
2番目のコンパイル時エラーが発生するのは、EndPoint型の変数が、インタフェースColorableを実装する値を参照できないためです。 これは、EndPointがfinal型で、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);
}
}
このプログラムはキャストを使用してコンパイルしますが、タイプに互換性がないため実行時に例外をスローします。
数値コンテキストは、算術演算子のオペランド、配列の作成およびアクセス式、条件式、およびswitch式の結果式に適用されます。
式が次のいずれかの場合、数値演算コンテキストに式が表示されます。
単項プラス演算子+、単項マイナス演算子-またはビット単位補足演算子~のオペランド(§15.15.3、§15.15.4、§15.15.5)
乗法演算子*、/または%のオペランド(§15.17)
数値型+または-の加算演算子または減算演算子のオペランド(§15.18.2)
シフト演算子<<、>>または>>>のオペランド(§15.19)。 これらのシフト演算子のオペランドは、グループとしてではなく別々に処理されます。 longシフト距離(右オペランド)は、シフトする値(左オペランド)をlongに昇格しません。
数値比較演算子<、<=、>または>=のオペランド(§15.20.1)
数値等価演算子==または!=のオペランド(§15.21.1)
ビット単位の整数演算子&、^または|のオペランド(§15.22.1)
式が次のいずれかの場合、数値配列コンテキストに式が表示されます。
式が次のいずれかの場合、数値選択コンテキストに式が表示されます。
数値昇格は、数値コンテキスト内のすべての式の昇格されたタイプを決定します。 プロモートされた型は、各式をプロモートされた型に変換できるように選択され、算術演算の場合は、プロモートされた型の値に対して演算が定義されます。 数値コンテキストでの式の順序は、数値プロモーションでは重要ではありません。 この規則は次のとおりです。
いずれかの式が参照型の場合、ボックス化解除変換の対象となります(§5.1.8)。
次に、次の規則に従って、プリミティブ変換の拡大(§5.1.2)およびプリミティブ変換の縮小(§5.1.3)がいくつかの式に適用されます。
いずれかの式がdouble型の場合、昇格された型はdoubleで、double型でない他の式はdoubleへのプリミティブ変換を拡張します。
それ以外の場合、いずれかの式がfloat型の場合、昇格された型はfloatで、float型でない他の式はfloatへのプリミティブ変換を拡張します。
それ以外の場合、いずれかの式がlong型の場合、昇格された型はlongで、long型でない他の式はlongへのプリミティブ変換を拡張します。
それ以外の場合、double、floatまたはlong型の式はありません。 この場合、プロモートされるタイプの選択方法はコンテキストの種類によって決まります。
数値算術コンテキストまたは数値配列コンテキストでは、昇格された型はintで、int型でない式は、intへのプリミティブ変換を拡張します。
数値選択コンテキストでは、次のルールが適用されます。
いずれかの式がint型で、定数式でない場合(§15.29)、昇格された型はintで、int型でない他の式はintへのプリミティブ変換を拡張します。
それ以外の場合、いずれかの式がshort型で、他のすべての式がshort型、byte型、またはshort型で表現可能な値を持つint型の定数式のいずれかである場合、昇格された型はshortで、byte式はshortへのプリミティブ変換を拡張し、int式はshortへのプリミティブ変換を絞り込みます。
それ以外の場合、いずれかの式がbyte型で、他のすべての式がbyte型であるか、byte型で表現可能な値を持つint型の定数式のいずれかである場合、昇格された型はbyteで、int式はbyteへのプリミティブ変換を絞り込みます。
それ以外の場合、いずれかの式がchar型で、他のすべての式がchar型であるか、char型で表現可能な値を持つint型の定数式のいずれかである場合、昇格された型はcharで、int式はcharへのプリミティブ変換を絞り込みます。
それ以外の場合、昇格された型はintで、int型でないすべての式は、intへのプリミティブ変換を拡張します。
単数プロモーションは、数値演算コンテキストまたは数値配列コンテキストで発生する単一の式に数値プロモーションを適用することで構成されます。
数値プロモーションは、数値演算コンテキストで発生する式のペアに数値プロモーションを適用することで構成されます。
一般的な数値プロモーションは、数値選択コンテキストで発生するすべての式に数値プロモーションを適用することで構成されます。
例5.6-1. 単項数値プロモーション
class Test {
public static void main(String[] args) {
byte b = 2;
int[] a = new int[b]; // dimension expression promotion
char c = '\u0001';
a[c] = 1; // index expression promotion
a[0] = -c; // unary - promotion
System.out.println("a: " + a[0] + "," + a[1]);
b = -1;
int i = ~b; // bitwise complement promotion
System.out.println("~0x" + Integer.toHexString(b)
+ "==0x" + Integer.toHexString(i));
i = b << 4L; // shift promotion (left operand)
System.out.println("0x" + Integer.toHexString(b)
+ "<<4L==0x" + Integer.toHexString(i));
}
}
このプログラムは出力を生成します:
a: -1,1 ~0xffffffff==0x0 0xffffffff<<4L==0xfffffff0
例5.6-2. バイナリ数値プロモーション
class Test {
public static void main(String[] args) {
int i = 0;
float f = 1.0f;
double d = 2.0;
// First int*float is promoted to float*float, then
// float==double is promoted to double==double:
if (i * f == d) System.out.println("oops");
// A char&byte is promoted to int&int:
byte b = 0x1f;
char c = 'G';
int control = c & b;
System.out.println(Integer.toHexString(control));
// Here int:float is promoted to float:float:
f = (b==0) ? i : 4.0f;
System.out.println(1.0/f);
}
}
このプログラムは出力を生成します:
7 0.25
この例では、文字の下位5ビットを除くすべてをマスキングして、ASCII文字GをASCII制御G (BEL)に変換します。 7は、この制御文字の数値です。
テスト・コンテキストでは、パターン一致演算子(§15.20.2)のオペランド、またはそのスイッチ・ブロック(§14.11.1)に関連付けられたパターンcaseラベルが少なくとも1つあるswitch式または文のセレクタ式を、パターン一致のプロセスの一部として型に変換できます。 パターン・マッチングは本質的に条件付きプロセス(§14.30.2)であるため、テスト・コンテキストは実行時に失敗する可能性のある変換を利用することが期待されます。
式がプリミティブ型の場合、テスト・コンテキストはアイデンティティ変換の使用を許可します(§5.1.1)。
式が参照型の場合は、テスト・コンテキストに次のいずれかを使用できます:
式にNULL型がある場合、式は任意の参照型に変換されることがあります。
テスト・コンテキストで絞込み参照変換が使用される場合、式の値のクラスに対してランタイム・チェックが実行され、ClassCastExceptionが発生する可能性があります。 それ以外の場合、実行時チェックは実行されません。