web-dev-qa-db-ja.com

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

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を生成します。コンパイラがこのエラーを検出できないのはなぜですか?

131
saravanan

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

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

コンパイラは、すべてを盲目的に受け入れるよりも少し賢く、異なる継承階層でオブジェクトをキャストしようとすると(たとえば、Dogを文字列にキャストすると)、コンパイラは、動作しない可能性があることを知っているので、それを投げ返します。

コンパイラーの不平を本質的に止めているだけなので、キャストするたびに、ifステートメント(またはその効果のあるもの)でClassCastExceptionを使用してinstanceofを引き起こさないことを確認することが重要です。

284
Michael Berry

理論的にはAnimal animalcanであるため:

Animal animal = new Dog();

一般に、ダウンキャストは良いアイデアではありません。避けるべきです。使用する場合は、チェックを含めることをお勧めします。

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

この種の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
}
38
Caumons

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

ここでは、コンパイラに「信頼してください。dDogオブジェクトを実際に参照しています」と言っていますが、そうではありません。 ダウンキャストを行うとコンパイラーは私たちを信頼することを強制されます

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

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

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

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

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

答えは、コンパイラができることは、2つの型が同じ継承ツリーにあることを確認することです。したがって、ダウンキャストの前に来たコードに応じて、animaldog型である可能性があります。

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

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

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;
}

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

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動物は、動物ができることを期待して、犬ができるからです。しかし、それは真実ではありません。

21
Zeeshan

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

Animal animal=new Animal();

ダウンキャスティングは、いくつかの理由によりJavaで許可されていません。詳細については、 here を参照してください。

3
Simeon

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

1
fresko

@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);
    }
}

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

ファーザーフィールド:ファーザーストリング

子フィールド:子文字列

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

0
Mehdi