スーパークラスからサブクラスへの明示的なキャスト


161
public class Animal {
    public void eat() {}
}

public class Dog extends Animal {
    public void eat() {}

    public void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = (Dog) animal;
    }
}

割り当てDog dog = (Dog) animal;はコンパイルエラーを生成しませんが、実行時にを生成しClassCastExceptionます。コンパイラがこのエラーを検出できないのはなぜですか?


50
あなたはコンパイラにエラーを検出しないように伝えています。
Mauricio

回答:


326

キャストを使用することで、本質的にコンパイラに「信頼してください。私はプロです。私は何をしているのか知っています。あなたが保証することはできませんが、これは animal変数は間違いなく、犬になる」

動物は実際には犬ではないので(それは動物であり、あなたはそうすることができAnimal animal = new Dog();、それは犬です)、その信頼に違反しているため、VMは実行時に例外をスローします(コンパイラーはすべて問題ないと言ったので、しない!)

コンパイラは、盲目的にすべてを受け入れるよりも少しスマートです。異なる継承階層でオブジェクトをキャストしようとすると(たとえば、DogをStringにキャストすると)、コンパイラーはそれをスローします。

基本的にコンパイラーが文句を言うのを止めるだけなので、キャストするたびに、ifステートメント(またはその効果に何か)をClassCastException使用instanceofすることで、a が発生しないことを確認することが重要です。


ありがとう、しかしあなたは犬が動物から伸びる必要がある、そうでないなら、働かない:)
2015

66
私はあなたがそれをどれほど劇的に鳴らしたかが大好きです
ヘンドラ・アングリアン

3
あなたはもちろん@deliveが、質問ごとに、Dog ないから延長Animal
マイケルベリー

52

理論的には犬になるAnimal animal ことできるので:

Animal animal = new Dog();

一般的に、ダウンキャストはお勧めできません。あなたはそれを避けるべきです。使用する場合は、チェックを含めることをお勧めします。

if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
}

しかし、次のコードはコンパイルエラーDog dog = new Animal();を生成します。(互換性のないタイプ)。しかし、この状況では、コンパイラーは、Animalがスーパークラスで、Dogがサブクラスであることを識別します。それは受け入れます。これについて私に説明してください
サラバン

3
はい、アニマルはスーパークラスです。すべての動物が犬であるとは限りませんよね?クラスは、そのタイプまたはスーパータイプでのみ参照できます。それらのサブタイプではありません。
Bozho

43

この種のClassCastExceptionを回避するために、

class A
class B extends A

Aのオブジェクトを取るコンストラクターをBで定義できます。このようにして、「キャスト」を行うことができます。例:

public B(A a) {
    super(a.arg1, a.arg2); //arg1 and arg2 must be, at least, protected in class A
    // If B class has more attributes, then you would initilize them here
}

24

マイケル・ベリーの答えを詳しく説明します。

Dog d = (Dog)Animal; //Compiles but fails at runtime

ここでは、コンパイラに「Trust me。私dは本当にDogオブジェクトを参照していることを知っている」と言っていますが、そうではありません。 ダウンキャストを行うとき、コンパイラーは私たちを信頼することを強いられることを覚えておいてください

コンパイラは、宣言された参照型のみを認識します。実行時のJVMは、オブジェクトが実際に何であるかを認識しています。

したがって、実行時のJVMが、Dog dが実際にはを参照しているのAnimalであって、Dogそれが言うオブジェクトを参照しているのではないことを理解したとき。ねえ...あなたはコンパイラに嘘をつき、大きな脂肪を投げClassCastExceptionます。

したがって、ダウンキャストしている場合は、instanceofテストを使用して失敗を回避する必要があります。

if (animal instanceof Dog) { Dog dog = (Dog) animal; }

今、疑問が浮かびます。なぜ地獄コンパイラは、最終的にそれがスローされるときにダウンキャストを許可しているのかjava.lang.ClassCastExceptionですか?

答えは、コンパイラができることは、2つの型が同じ継承ツリーにあることを確認することだけなので、ダウンキャストの前にコードが何であるかに応じてanimal、型が可能であるということですdog

コンパイラーは、実行時に機能する可能性があるものを許可する必要があります。

次のコードスニペットを検討してください。

public static void main(String[] args) 
{   
    Dog d = getMeAnAnimal();// ERROR: Type mismatch: cannot convert Animal to Dog
    Dog d = (Dog)getMeAnAnimal(); // Downcast works fine. No ClassCastException :)
    d.eat();

}

private static Animal getMeAnAnimal()
{
    Animal animal = new Dog();
    return animal;
}

ただし、キャストが機能しない可能性があるとコンパイラーが確信している場合、コンパイルは失敗します。IE異なる継承階層でオブジェクトをキャストしようとした場合

String s = (String)d; // ERROR : cannot cast for Dog to String

ダウンキャスティングとは異なり、アップキャスティングは暗黙的に機能します。これは、アップキャスティングすると、呼び出すことができるメソッドの数を暗黙的に制限するためです。

Dog d = new Dog(); Animal animal1 = d; // Works fine with no explicit cast Animal animal2 = (Animal) d; // Works fine with n explicit cast

上記の両方のアップキャストは例外なく正常に機能します。なぜなら、犬はIS-A動物であり、動物ができること、犬ができることを信じているからです。しかし、それは真の逆ではありません。


1

インスタンスタイプが動物であるため、コードはコンパイルエラーを生成します。

Animal animal=new Animal();

いくつかの理由により、Javaではダウンキャストは許可されていません。詳細はこちらをご覧ください。


5
コンパイルエラーはありません。それが彼の質問の理由です
Clarence Liu

1

説明したように、それは不可能です。サブクラスのメソッドを使用する場合は、メソッドをスーパークラス(空の場合もある)に追加できるかどうかを評価し、ポリモーフィズムのおかげで、希望する動作(サブクラス)を得るサブクラスから呼び出します。したがって、d.method()を呼び出すと、キャストは失敗しますが、オブジェクトが犬ではない場合、問題はありません。


0

@Caumonsの答えを作成するには:

1つの父親クラスに多くの子供がいて、そのクラスに共通フィールドを追加する必要があるとします。上記のアプローチを検討する場合は、各子クラスに1つずつ移動し、新しいフィールドのコンストラクターをリファクタリングする必要があります。したがって、このシナリオは、このシナリオでは有望なソリューションではありません

次に、このソリューションを見てみましょう。

父親は各子供から自己オブジェクトを受け取ることができます。ここに父親クラスがあります:

public class Father {

    protected String fatherField;

    public Father(Father a){
        fatherField = a.fatherField;
    }

    //Second constructor
    public Father(String fatherField){
        this.fatherField = fatherField;
    }

    //.... Other constructors + Getters and Setters for the Fields
}

以下は、その親コン​​ストラクタの1つを実装する必要がある子クラスです。この場合、前述のコンストラクタです。

public class Child extends Father {

    protected String childField;

    public Child(Father father, String childField ) {
        super(father);
        this.childField = childField;
    }

    //.... Other constructors + Getters and Setters for the Fields

    @Override
    public String toString() {
        return String.format("Father Field is: %s\nChild Field is: %s", fatherField, childField);
    }
}

次に、アプリケーションをテストします。

public class Test {
    public static void main(String[] args) {
        Father fatherObj = new Father("Father String");
        Child child = new Child(fatherObj, "Child String");
        System.out.println(child);
    }
}

そしてここに結果があります:

父のフィールドは:父のひも

子フィールドは:子文字列

これで、子コードが壊れることを心配することなく、新しいフィールドを親クラスに簡単に追加できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.