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
を生成します。コンパイラがこのエラーを検出できないのはなぜですか?
キャストを使用することで、コンパイラに「信頼してください。私はプロです。私は自分がやっていることを知っています。保証することはできませんが、このanimal
変数は間違いなく犬になります。」
動物は実際には犬ではないので(動物であるため、Animal animal = new Dog();
を実行でき、犬になります)、VMは、その信頼に違反したため、実行時に例外をスローします(コンパイラはすべて大丈夫だろうが、そうではない!)
コンパイラは、すべてを盲目的に受け入れるよりも少し賢く、異なる継承階層でオブジェクトをキャストしようとすると(たとえば、Dogを文字列にキャストすると)、コンパイラは、動作しない可能性があることを知っているので、それを投げ返します。
コンパイラーの不平を本質的に止めているだけなので、キャストするたびに、ifステートメント(またはその効果のあるもの)でClassCastException
を使用してinstanceof
を引き起こさないことを確認することが重要です。
理論的にはAnimal animal
canであるため:
Animal animal = new Dog();
一般に、ダウンキャストは良いアイデアではありません。避けるべきです。使用する場合は、チェックを含めることをお勧めします。
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
}
この種の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
}
Dog d = (Dog)Animal; //Compiles but fails at runtime
ここでは、コンパイラに「信頼してください。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;
}
ただし、キャストが機能しないとコンパイラーが確信している場合、コンパイルは失敗します。 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動物は、動物ができることを期待して、犬ができるからです。しかし、それは真実ではありません。
インスタンスタイプは動物であるため、コードはコンパイルエラーを生成します。
Animal animal=new Animal();
ダウンキャスティングは、いくつかの理由によりJavaで許可されていません。詳細については、 here を参照してください。
説明したように、それは不可能です。サブクラスのメソッドを使用する場合は、メソッドをスーパークラスに追加する可能性を評価し(空の場合もあります)、ポリモーフィズムのおかげで、必要な動作(サブクラス)を取得するサブクラスから呼び出します。したがって、d.method()を呼び出すと、呼び出しは成功しますが、オブジェクトが犬ではない場合、問題はありません。
@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);
}
}
そして、ここに結果があります:
ファーザーフィールド:ファーザーストリング
子フィールド:子文字列
これで、子供のコードが壊れることを心配することなく、新しいフィールドを簡単に父親クラスに追加できます。