15 instanceofおよびswitchによる安全なキャスト

instanceof演算子を使用して、ある型から別の型へのキャストが安全かどうかを実行時に確認できます。キャストまたは変換中に情報が失われたり、例外がスローされたりしない場合、キャストまたは変換は安全です。instanceof演算子を使用して、2つの参照型または2つのプリミティブ型の間でキャストが安全かどうかをテストできます。また、変換の安全性は、switch文および式の動作に影響します。

ノート:

instanceof演算子を使用して、2つの参照型の間だけでなく、2つの型の間でキャストが安全かどうかをテストする機能は、プレビュー機能です。プレビュー機能は、設計、仕様および実装が完了したが、永続的でない機能です。プレビュー機能は、将来のJava SEリリースで、異なる形式で存在することもあれば、まったく存在しないこともあります。プレビュー機能が含まれているコードをコンパイルして実行するには、追加のコマンド行オプションを指定する必要があります。『Preview Language and VM Features』を参照してください。

詳細は、JEP 530: パターン、instanceofおよびswitchのプリミティブ型(第4プレビュー)を参照してください。

次の例では、instanceofを型比較演算子として使用して、Shape sCircleかどうかをテストします。Circleである場合、Shape sCircleにキャストすることは安全です。つまり、出力できる半径が間違いなく存在します:

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であるShapeCircleにキャストしようとしたときに、例外をスローします:

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をスローすることはありません。値を変換した結果(型に互換性がある場合)、値の全体的な大きさと精度および範囲に関する情報が失われる可能性があります。次の例では、intbyteにキャストします:

int i = 2345323;
byte b = (byte)i;
System.out.println(b);

次のように出力されます。

107

2345323から107のように不適切に、または意図せず変換された値、あるいは2.05fから2のようにわずかに変換された値は、プログラム全体に静かに伝播して、見つけにくいbugの原因になる可能性があります。

プリミティブ型のintbyteは範囲が異なるため、値をキャストする前にそれをテストできます:

if (i >= -128 && i <= 127) {
    System.out.println((byte)i);
}

ただし、2つのプリミティブ型間のキャストが安全かどうかをより明確かつ簡潔にテストする方法は、instanceofを型比較演算子として使用することです:

if (i instanceof byte) {
    System.out.println((byte)i);
}

この例では、依然として値ibyteにキャストする必要があります。instanceof型比較演算子は、ibyteかどうかをチェックするのみです。実際のキャストは実行しません。

instanceofをパターン一致演算子として使用する方法もありますが、その方法には、テストが成功した場合にキャストが実行されるという追加の利点があります:

if (i instanceof byte b) {
    System.out.println(b);
}

変換の安全性とswitch

次に例を示します。

void safeConversion(float f) {
    switch (f) {
        case double d -> System.out.println("As double: " + d);
    }
}

caseラベルcase double dは、switch文のセレクタ式のfloat値をdouble値に変換します。この変換は安全です。つまり、変換中に、値の全体的な大きさと精度および範囲に関する情報が失われる可能性はありません。このswitch文は、すべてのfloat値に対して機能します。

次の例を考えてみましょう。これはコンパイルされません。

void unsafeConversion(float f) {
    // error: the switch statement does not cover all
    // possible input values
    switch (f) {
        case int i -> System.out.println("As int: " + i);
    }
}

caseラベルcase int iは、switch文のセレクタ式のfloat値をint値に変換します。この変換は常に安全であるとはかぎりません。つまり、値の全体的な大きさと精度および範囲に関する情報が失われる可能性があります。このswitch文が許可された場合、100.01fから100のように不適切に、または意図せず変換された値がプログラム全体に伝播して、見つけにくいバグの原因となる可能性があります。このような状況が発生するのを防ぐために、コンパイラはエラーを生成します。

safeConversionの例では、float値からdouble値への変換は無条件に正確な変換であり、これは、変換で実行時に情報が失われないことがコンパイル時に認識されていることを意味します。プリミティブ型間の変換が無条件に完全であるかどうかは、switchの網羅性およびパターンの他のパターンに対する優位性に影響します。詳細は、「switchの網羅性」および「優位性と無条件に正確な変換」を参照してください。

ノート:

float値をdouble値に変換することは、型ベースの無条件に正確な変換の例です。これは、変換する式の値およびターゲット型に関係なく情報が失われない変換です。値ベースの無条件に正確な変換と呼ばれる別の種類の無条件に正確な変換があります。これは、変換される式の型とターゲット型のために一般に情報が失われる可能性があるが、変換される値が定数式の変換です。このような場合、コンパイラは、変換によって情報が失われないかどうかをテストできます。たとえば、int42byte値に変換することは、無条件に正確な変換です。コンパイラは、この種の変換により、定数である他のcaseラベルより優位なcaseラベルを検出できます。例については、「優位性と無条件に正確な変換」を参照してください。

詳細は、Java言語仕様変換のテストの正確性に関する項を参照してください。