14 instanceofおよびswitchによる安全なキャスト
instanceof
演算子を使用して、ある型から別の型へのキャストが安全かどうかを実行時に確認できます。キャストまたは変換中に情報が失われたり、例外がスローされたりしない場合、キャストまたは変換は安全です。instanceof
演算子を使用して、2つの参照型または2つのプリミティブ型の間でキャストが安全かどうかをテストできます。また、変換の安全性は、switch
文および式の動作に影響します。
ノート:
instanceof
演算子を使用して2つのプリミティブ型の間でキャストが安全かどうかをテストする機能は、プレビュー機能です。プレビュー機能は、設計、仕様および実装が完了したが、永続的でない機能です。プレビュー機能は、将来のJava SEリリースで、異なる形式で存在することもあれば、まったく存在しないこともあります。プレビュー機能が含まれているコードをコンパイルして実行するには、追加のコマンド行オプションを指定する必要があります。『Preview Language and VM Features』を参照してください。
詳細は、JEP 455: パターン、instanceofおよびswitchでのプリミティブ型(プレビュー)を参照してください。
次の例では、instanceof
を型比較演算子として使用して、Shape s
がCircle
かどうかをテストします。Circleである場合、Shape s
をCircle
にキャストすることは安全です。つまり、出力できる半径が間違いなく存在します:
public interface Shape { }
record Rectangle(double length, double width) implements Shape { }
record Circle(double radius) implements Shape { }
//...
Shape s = new Rectangle(4,3);
if (s instanceof Circle) {
Circle c = (Circle)s;
System.out.println("Radius: " + c.radius());
}
Javaランタイムは、参照型を別の参照型にキャストできない場合、ClassCastException
をスローします。s instanceof Circle
式をこの例から削除した場合、Javaランタイムは、Rectangle
であるShape
をCircle
にキャストしようとしたときに、例外をスローします:
Shape s = new Rectangle(4,3);
// ClassCastException: class Rectangle cannot be cast
// to class Circle
Circle c = (Circle)s;
System.out.println("Radius: " + c.radius());
プリミティブ型間のキャストの場合、JavaランタイムがClassCastException
をスローすることはありません。値を変換した結果(型に互換性がある場合)、値の全体的な大きさと精度および範囲に関する情報が失われる可能性があります。次の例では、int
をbyte
にキャストします:
int i = 2345323;
byte b = (byte)i;
System.out.println(b);
次のように出力されます。
107
2345323
から107
のように不適切に、または意図せず変換された値、あるいは2.05f
から2
のようにわずかに変換された値は、プログラム全体に静かに伝播して、見つけにくいbugの原因になる可能性があります。
プリミティブ型のint
とbyte
は範囲が異なるため、値をキャストする前にそれをテストできます:
if (i >= -128 && i <= 127) {
System.out.println((byte)i);
}
ただし、2つのプリミティブ型間のキャストが安全かどうかをより明確かつ簡潔にテストする方法は、instanceof
を型比較演算子として使用することです:
if (i instanceof byte) {
System.out.println((byte)i);
}
この例では、依然として値i
をbyte
にキャストする必要があります。instanceof
型比較演算子は、i
がbyte
かどうかをチェックするのみです。実際のキャストは実行しません。
instanceof
をパターン一致演算子として使用する方法もありますが、その方法には、テストが成功した場合にキャストが実行されるという追加の利点があります:
if (i instanceof byte b) {
System.out.println(b);
}
変換の安全性とswitch
2つのswitch
文を含む次の例について考えてみます。コンパイラは、最初のswitch
文に対してエラーを生成します:
float f = 100.01f;
switch (f) {
// error: the switch statement does not cover
// all possible input values
case int i ->
System.out.println(i + " as an int ");
}
switch (f) {
case double d ->
System.out.println(d + " as a double");
}
最初のswitch
文は、int
値のみを処理するため、網羅的ではありません。caseラベルcase int i
は、switch
文のセレクタ式のfloat
値をint
値に変換します。この変換は安全ではありません。つまり、値の全体的な大きさと精度および範囲に関する情報が失われる可能性があります。このswitch
文が許可されている場合、100.01f
から100
のように不適切に、または意図せず変換された値は、プログラム全体に伝播して、見つけにくいbugの原因になる可能性があります。このような状況が発生するのを防ぐために、コンパイラはエラーを生成します。
2番目のswitch
文は網羅的です。ラベルdouble d
は、セレクタ式のfloat
をdouble
に変換します。これは無条件に正確な変換です。つまり、最初の型から2番目の型への変換によって値に関する情報が失われたり、例外がスローされたりしないことが保証されているとコンパイル時にわかっています。無条件に正確な変換の例としては、byte
からint
への変換、byte
からByte
への変換、int
からlong
への変換、StringからObjectへの変換などがあります。
プリミティブ型間の変換が無条件に正確であるかどうかは、パターンが他のパターンよりどのように優先されるかにも影響します。(「パターン・ラベルの優位性」を参照してください。)次の例では、コンパイラは型パターンbyte b
に関するエラーを生成します:
switch (f) {
case int i ->
System.out.println(i + " as an int");
// error: this case label is dominated by a preceding case label
case byte b ->
System.out.println(b + " as a byte");
default ->
System.out.println(f + " as a float");
}
型パターンint i
は、byte
からint
への変換が無条件に正確であるため、型パターンbyte b
より優先されます。