casting eksplisit dari super class ke subclass

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

Tugas Dog dog = (Dog) animal;tidak menghasilkan kesalahan kompilasi, tetapi pada saat runtime ia menghasilkan a ClassCastException. Mengapa kompiler tidak dapat mendeteksi kesalahan ini?

saravanan
sumber
51
ANDA memberi tahu kompiler untuk TIDAK mendeteksi kesalahan.
Mauricio

Jawaban:

326

Dengan menggunakan gips Anda pada dasarnya mengatakan kepada kompiler "percayalah padaku. Saya seorang profesional, saya tahu apa yang saya lakukan dan saya tahu bahwa meskipun Anda tidak dapat menjaminnya, saya memberi tahu Anda bahwa animalvariabel ini pasti akan menjadi seekor anjing. "

Karena hewan itu sebenarnya bukan anjing (itu hewan, Anda bisa melakukannya Animal animal = new Dog();dan itu akan menjadi anjing) VM melempar pengecualian saat runtime karena Anda telah melanggar kepercayaan itu (Anda mengatakan kepada kompiler semuanya akan baik-baik saja dan itu tidak!)

Kompiler sedikit lebih pintar daripada hanya menerima secara membabi buta segalanya, jika Anda mencoba dan melemparkan objek dalam hierarki pewarisan yang berbeda (misalnya, melempar Anjing ke Tali), maka kompiler akan mengembalikannya kepada Anda karena ia tahu bahwa itu tidak akan pernah bisa berfungsi.

Karena pada dasarnya Anda hanya menghentikan kompiler agar tidak mengeluh, setiap kali Anda memberikannya penting untuk memeriksa bahwa Anda tidak akan menyebabkan ClassCastExceptiondengan menggunakan instanceofdalam pernyataan if (atau sesuatu untuk efek itu.)

Michael Berry
sumber
Terima kasih tetapi Anda perlu anjing memperpanjang dari Hewan jika tidak, tidak bekerja :)
delive
66
Saya suka betapa dramatisnya Anda membuatnya
Hendra Anggrian
3
@delive Tentu saja Anda lakukan, tetapi sesuai pertanyaan, Dog tidak diperpanjang dari Animal!
Michael Berry
52

Karena secara teori Animal animal bisa menjadi anjing:

Animal animal = new Dog();

Umumnya, downcasting bukanlah ide yang baik. Anda harus menghindarinya. Jika Anda menggunakannya, Anda sebaiknya menyertakan cek:

if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
}
Bozho
sumber
tetapi kode berikut menghasilkan kesalahan kompilasi Dog dog = new Animal (); (jenis yang tidak kompatibel) .tapi dalam situasi ini kompiler mengidentifikasi Hewan adalah kelas super dan Anjing adalah subkelas. jadi tugasnya salah. itu menerima. tolong jelaskan saya tentang ini
saravanan
3
ya, karena Animal adalah superclass. Tidak setiap binatang adalah Anjing, bukan? Anda bisa merujuk ke kelas hanya dengan tipe atau supertipe mereka. Bukan subtipe mereka.
Bozho
43

Untuk menghindari ClassCastException semacam ini, jika Anda memiliki:

class A
class B extends A

Anda dapat mendefinisikan konstruktor dalam B yang mengambil objek A. Dengan cara ini kita bisa melakukan "pemeran" misalnya:

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
}
Caumons
sumber
24

Menguraikan jawaban yang diberikan oleh Michael Berry.

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

Di sini Anda mengatakan kepada kompiler "Percayalah. Saya tahu dbenar-benar mengacu pada Dogobjek" meskipun tidak. Ingat kompiler dipaksa untuk mempercayai kami ketika kami melakukan downcast .

Kompilator hanya tahu tentang tipe referensi yang dideklarasikan. JVM saat runtime tahu apa objek sebenarnya.

Jadi ketika JVM pada saat runtime mengetahui bahwa Dog dsebenarnya mengacu pada Animaldan bukan Dogobjek yang dikatakannya. Hei ... kau berbohong ke kompiler dan membuang banyak lemak ClassCastException.

Jadi, jika Anda downcasting Anda harus menggunakan instanceoftes untuk menghindari mengacaukan.

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

Sekarang sebuah pertanyaan muncul di benak kami. Mengapa kompiler itu membiarkan yang tertunduk ketika akhirnya akan membuang java.lang.ClassCastException?

Jawabannya adalah yang dapat dilakukan oleh semua kompiler adalah memverifikasi bahwa kedua jenis berada di pohon warisan yang sama, jadi tergantung pada kode apa pun yang mungkin datang sebelum downcast, mungkin animaljenisnya dog.

Kompiler harus mengizinkan hal-hal yang mungkin dapat berfungsi saat runtime.

Pertimbangkan snipet kode berikut:

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

Namun, jika kompiler yakin bahwa para pemain tidak akan bekerja, kompilasi akan gagal. IE Jika Anda mencoba untuk melemparkan objek dalam hierarki warisan yang berbeda

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

Tidak seperti downcasting, upcasting bekerja secara implisit karena ketika Anda membuangnya Anda secara implisit membatasi jumlah metode yang dapat Anda gunakan, sebagai kebalikan dari downcasting, yang menyiratkan bahwa di kemudian hari, Anda mungkin ingin menggunakan metode yang lebih spesifik.

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

Kedua hal yang disebutkan di atas akan bekerja dengan baik tanpa kecuali karena Anjing IS-A Animal, anithing an Animal dapat melakukan, seekor anjing dapat melakukannya. Tetapi itu tidak benar vica-versa.

Zeeshan
sumber
1

Kode menghasilkan kesalahan kompilasi karena tipe instance Anda adalah Hewan:

Animal animal=new Animal();

Downcasting tidak diperbolehkan di Jawa karena beberapa alasan. Lihat di sini untuk detailnya.

Simeon
sumber
5
Tidak ada kesalahan kompilasi, itulah alasan pertanyaannya
Clarence Liu
1

Seperti yang dijelaskan, itu tidak mungkin. Jika Anda ingin menggunakan metode subclass, evaluasi kemungkinan untuk menambahkan metode ke superclass (mungkin kosong) dan panggilan dari subclass untuk mendapatkan perilaku yang Anda inginkan (subclass) berkat polimorfisme. Jadi, ketika Anda menelepon d.method () panggilan akan berhasil tanpa casting, tetapi jika objeknya bukan anjing, tidak akan ada masalah

fresko
sumber
0

Untuk mengembangkan jawaban @Caumons:

Bayangkan satu kelas ayah memiliki banyak anak dan ada kebutuhan untuk menambahkan bidang umum ke dalam kelas itu. Jika Anda mempertimbangkan pendekatan yang disebutkan, Anda harus pergi ke setiap anak kelas satu per satu dan refactor konstruktor mereka untuk bidang baru. Oleh karena itu solusi itu bukanlah solusi yang menjanjikan dalam skenario ini

Sekarang lihat solusi ini.

Seorang ayah dapat menerima objek diri dari setiap anak. Ini adalah kelas ayah:

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
}

Berikut adalah kelas anak kami yang harus mengimplementasikan salah satu konstruktor ayahnya, dalam hal ini konstruktor tersebut:

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

Sekarang kami menguji aplikasi:

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

Dan inilah hasilnya:

Father Field adalah: Father String

Field Anak adalah: String Anak

Sekarang Anda dapat dengan mudah menambahkan bidang baru ke kelas ayah tanpa khawatir kode anak Anda akan rusak;

Mehdi
sumber