явное приведение от суперкласса к подклассу

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. Почему компилятор не может обнаружить эту ошибку?


person saravanan    schedule 01.02.2011    source источник
comment
ВЫ говорите компилятору НЕ обнаруживать ошибку.   -  person Mauricio    schedule 01.02.2011


Ответы (7)


Используя приведение типов, вы, по сути, говорите компилятору: «Поверьте мне. Я профессионал, я знаю, что делаю, и я знаю, что хотя вы и не можете этого гарантировать, я говорю вам, что эта переменная animal обязательно будет собакой».

Поскольку животное на самом деле не собака (это животное, вы можете сделать Animal animal = new Dog();, и это будет собака), виртуальная машина выдает исключение во время выполнения, потому что вы нарушили это доверие (вы сказали компилятору, что все будет в порядке, и это не!)

Компилятор немного умнее, чем просто слепо принимать все, если вы попытаетесь преобразовать объекты в разные иерархии наследования (например, преобразовать Dog в String), тогда компилятор вернет вам это, потому что он знает, что это никогда не может работать.

Поскольку вы, по сути, просто предотвращаете жалобы компилятора, каждый раз, когда вы выполняете приведение, важно проверять, что вы не вызовете ClassCastException, используя instanceof в операторе if (или что-то в этом роде).

person Michael Berry    schedule 01.02.2011
comment
Спасибо, но вам нужно расширение собаки от животного, если нет, то не работает :) - person ; 09.12.2015
comment
@delive Конечно, да, но, согласно вопросу, Dog действительно простирается от Animal! - person Michael Berry; 25.05.2017

Потому что теоретически Animal animal может быть собакой:

Animal animal = new Dog();

Как правило, опускание - не лучшая идея. Вам следует избегать этого. Если вы используете его, вам лучше включить проверку:

if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
}
person Bozho    schedule 01.02.2011
comment
но следующий код генерирует ошибку компиляции Dog dog=new Animal(); (несовместимые типы). Но в этой ситуации компилятор определяет, что Animal является суперклассом, а Dog — подклассом. Таким образом, назначение неверно. Но когда мы бросаем Dog dog = (Dog) animal; он принимает. пожалуйста, объясните мне об этом - person saravanan; 01.02.2011
comment
да, потому что Animal — это суперкласс. Не каждое животное - Собака, верно? Вы можете ссылаться на классы только по их типам или супертипам. Не их подвиды. - person Bozho; 01.02.2011

Чтобы избежать такого рода ClassCastException, если у вас есть:

class A
class B extends A

Вы можете определить конструктор в B, который принимает объект A. Таким образом, мы можем выполнить «приведение», например:

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
}
person Caumons    schedule 12.02.2012

Разработка ответа, данного Майклом Берри.

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?

Ответ заключается в том, что все, что может сделать компилятор, — это проверить, находятся ли эти два типа в одном и том же дереве наследования, поэтому в зависимости от того, какой код мог появиться перед преобразованием вниз, возможно, что 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;
}

Однако, если компилятор уверен, что приведение не сработает, компиляция завершится ошибкой. т.е. Если вы пытаетесь привести объекты в разные иерархии наследования

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

Оба приведенных выше приведения будут работать нормально без каких-либо исключений, потому что Собака ЕСТЬ-Животное, и все, что может сделать Животное, может сделать и собака. Но это не так наоборот.

person Zeeshan    schedule 23.06.2014

Код генерирует ошибку компиляции, потому что ваш тип экземпляра — животное:

Animal animal=new Animal();

Понижающее приведение не разрешено в Java по нескольким причинам. Подробнее см. здесь.

person Simeon    schedule 01.02.2011
comment
Ошибки компиляции нет, вот и причина его вопроса - person Clarence Liu; 02.01.2014

Как объяснили, это невозможно. Если вы хотите использовать метод подкласса, оцените возможность добавления метода в суперкласс (может быть пустым) и вызывайте из подклассов, получая желаемое поведение (подкласс) благодаря полиморфизму. Поэтому, когда вы вызываете d.method(), вызов будет успешным без приведения типов, но в случае, если объект будет не собакой, проблемы не будет.

person fresko    schedule 18.02.2015

Чтобы разработать ответ @Caumons:

Представьте, что у одного родительского класса много потомков, и вам нужно добавить в этот класс общее поле. Если вы рассматриваете упомянутый подход, вы должны перейти к каждому дочернему классу один за другим и реорганизовать их конструкторы для нового поля. поэтому это решение не является многообещающим решением в этом сценарии

Теперь взгляните на это решение.

Отец может получить самообъект от каждого ребенка. Вот класс отца:

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
}

Вот наш дочерний класс, который должен реализовать один из своих родительских конструкторов, в данном случае вышеупомянутый конструктор:

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

И вот результат:

Отец Поле: Отец Строка

Дочернее поле: дочерняя строка

Теперь вы можете легко добавлять новые поля в родительский класс, не беспокоясь о том, что ваши дочерние коды могут сломаться;

person Mehdi    schedule 23.11.2018