đúc rõ ràng từ siêu lớp đến lớp con


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

Việc gán Dog dog = (Dog) animal;không tạo ra lỗi biên dịch, nhưng trong thời gian chạy, nó tạo ra một ClassCastException. Tại sao trình biên dịch không thể phát hiện lỗi này?


50
BẠN đang nói với trình biên dịch KHÔNG phát hiện lỗi.
Mauricio

Câu trả lời:


325

Bằng cách sử dụng một diễn viên, về cơ bản bạn đang nói với trình biên dịch "hãy tin tôi. Tôi là một người chuyên nghiệp, tôi biết những gì tôi đang làm và tôi biết rằng mặc dù bạn không thể đảm bảo điều đó, tôi nói với bạn rằng animalbiến này chắc chắn là sẽ trở thành một con chó. "

Vì con vật không thực sự là một con chó (nó là một con vật, bạn có thể làm Animal animal = new Dog();và nó sẽ là một con chó), VM ném một ngoại lệ trong thời gian chạy vì bạn đã vi phạm sự tin tưởng đó (bạn nói với trình biên dịch mọi thứ sẽ ổn và nó không phải!)

Trình biên dịch thông minh hơn một chút so với việc chấp nhận một cách mù quáng mọi thứ, nếu bạn thử và truyền các đối tượng theo các hệ thống phân cấp kế thừa khác nhau (ví dụ như chuyển Dog thành Chuỗi) thì trình biên dịch sẽ ném lại cho bạn vì nó biết rằng nó không bao giờ có thể hoạt động.

Bởi vì về cơ bản, bạn chỉ ngăn trình biên dịch phàn nàn, mỗi khi bạn sử dụng, điều quan trọng là kiểm tra xem bạn sẽ không gây ra ClassCastExceptionbằng cách sử dụng instanceoftrong câu lệnh if (hoặc một cái gì đó cho hiệu ứng đó.)


Cảm ơn nhưng bạn cần chó kéo dài từ Animal nếu không, không hoạt động :)
delive 9/12/2015

65
Tôi yêu cách bạn tạo ra âm thanh kịch tính
Hendra Anggrian

3
@delive Tất nhiên là bạn làm, nhưng theo câu hỏi, Dog sẽ mở rộng từ Animal!
Michael Berry

52

Bởi vì về mặt lý thuyết Animal animal có thể là một con chó:

Animal animal = new Dog();

Nói chung, downcasting không phải là một ý tưởng tốt. Bạn nên tránh nó. Nếu bạn sử dụng nó, tốt hơn bạn nên bao gồm một kiểm tra:

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

nhưng đoạn mã sau tạo lỗi biên dịch Dog dog = new Animal (); (các loại không tương thích) .nhưng trong trình biên dịch tình huống này xác định Animal là một siêu lớp và Dog là một lớp con. Vì vậy, việc gán sai. nó chấp nhận. xin vui lòng giải thích cho tôi về điều này
saravanan

3
vâng, bởi vì Động vật là siêu lớp. Không phải mọi động vật là một con chó, phải không? Bạn có thể tham khảo các lớp chỉ theo loại hoặc siêu kiểu của chúng. Không phải kiểu phụ của họ.
Bozho

43

Để tránh loại ClassCastException này, nếu bạn có:

class A
class B extends A

Bạn có thể định nghĩa một hàm tạo trong B lấy một đối tượng là A. Bằng cách này chúng ta có thể thực hiện "cast", vd:

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

Xây dựng câu trả lời được đưa ra bởi Michael Berry.

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

Ở đây bạn đang nói với trình biên dịch "Hãy tin tôi. Tôi biết dlà thực sự đang đề cập đến một Dogđối tượng" mặc dù không phải vậy. Hãy nhớ rằng trình biên dịch buộc phải tin tưởng chúng tôi khi chúng tôi thực hiện một chương trình truyền hình .

Trình biên dịch chỉ biết về kiểu tham chiếu khai báo. JVM trong thời gian chạy biết đối tượng thực sự là gì.

Vì vậy, khi JVM tại thời gian chạy chỉ ra rằng Dog dthực sự đang đề cập đến một Animalvà không phải là một Dogđối tượng mà nó nói. Này ... bạn đã nói dối trình biên dịch và ném một lượng lớn chất béo ClassCastException.

Vì vậy, nếu bạn đang downcasting, bạn nên sử dụng instanceofthử nghiệm để tránh làm hỏng.

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

Bây giờ một câu hỏi đến với tâm trí của chúng tôi. Tại sao trình biên dịch địa ngục lại cho phép downcast khi cuối cùng nó sẽ ném một java.lang.ClassCastException?

Câu trả lời là tất cả các trình biên dịch có thể làm là xác minh rằng hai loại nằm trong cùng một cây thừa kế, do đó tùy thuộc vào bất kỳ mã nào có thể xuất hiện trước khi phát sóng, có thể đó animallà loại dog.

Trình biên dịch phải cho phép những thứ có thể hoạt động trong thời gian chạy.

Hãy xem xét đoạn mã sau:

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

Tuy nhiên, nếu trình biên dịch chắc chắn rằng diễn viên sẽ không thể hoạt động, quá trình biên dịch sẽ thất bại. IE Nếu bạn cố gắng truyền các đối tượng theo các phân cấp thừa kế khác nhau

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

Không giống như downcasting, upcasting hoạt động hoàn toàn bởi vì khi bạn upcast bạn hoàn toàn hạn chế số lượng phương thức bạn có thể gọi, ngược lại với downcasting, ngụ ý rằng sau này, bạn có thể muốn gọi một phương thức cụ thể hơn.

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

Cả hai phần u ám ở trên sẽ hoạt động tốt mà không có ngoại lệ nào bởi vì Chó IS-A Animal, anithing Animal có thể làm, một con chó có thể làm. Nhưng đó không phải là vica-Versa.


1

Mã này tạo ra lỗi biên dịch vì loại thể hiện của bạn là Animal:

Animal animal=new Animal();

Downcasting không được phép trong Java vì một số lý do. Xem ở đây để biết chi tiết.


5
Không có lỗi biên dịch, đó là lý do cho câu hỏi của anh ấy
Clarence Liu

1

Như đã giải thích, nó là không thể. Nếu bạn muốn sử dụng một phương thức của lớp con, hãy đánh giá khả năng thêm phương thức vào lớp cha (có thể trống) và gọi từ các lớp con nhận hành vi bạn muốn (lớp con) nhờ đa hình. Vì vậy, khi bạn gọi d.method () cuộc gọi sẽ thành công với việc truyền tải, nhưng trong trường hợp đối tượng sẽ không phải là một con chó, sẽ không có vấn đề gì


0

Để phát triển câu trả lời của @Caumons:

Hãy tưởng tượng một lớp cha có nhiều con và cần phải thêm một lĩnh vực chung vào lớp đó. Nếu bạn xem xét phương pháp đã đề cập, bạn nên lần lượt đến từng lớp trẻ và cấu trúc lại các hàm tạo của chúng cho trường mới. do đó, giải pháp đó không phải là một giải pháp đầy hứa hẹn trong kịch bản này

Bây giờ hãy xem giải pháp này.

Một người cha có thể nhận được một đối tượng tự từ mỗi đứa trẻ. Đây là một lớp cha:

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
}

Đây là lớp con của chúng ta sẽ thực hiện một trong các hàm tạo của cha, trong trường hợp này là hàm tạo đã nói ở trên:

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

Bây giờ chúng tôi thử nghiệm ứng dụng:

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

Và đây là kết quả :

Trường cha là: Chuỗi cha

Trường con là: Chuỗi con

Bây giờ bạn có thể dễ dàng thêm các lĩnh vực mới vào lớp cha mà không phải lo lắng về việc mã con cái của bạn bị phá vỡ;

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.