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



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

666   6  

6 ответов:

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

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

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

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

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

Animal animal = new Dog();

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

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

Чтобы избежать такого рода 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
}

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

В отличие от приведения типов, upcasting подспудно работает, потому что когда вы upcast вы неявно ограничиваете число методов, которые вы можете вызвать, в отличие от даункастинга, который подразумевает, что позже вы можете захотеть вызвать более конкретный метод.

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

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

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

Animal animal=new Animal();

Приведения типов не допускается в Java по нескольким причинам. Подробнее см. здесь.

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

Comments

    Ничего не найдено.