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