Явное литье из суперкласса в подкласс

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

Ответ 1

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

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

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

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

Ответ 2

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

Animal animal = new Dog();

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

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

Ответ 3

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

Ответ 4

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

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

В отличие от downcasting, 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

Оба вышеперечисленных upcast будут работать нормально без каких-либо исключений, потому что Dog IS-A Animal, anithing Animal может делать, собака может делать. Но это не правда, наоборот.

Ответ 5

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

Animal animal=new Animal();

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

Ответ 6

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

Ответ 7

Чтобы разработать ответ @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);
    }
}

А вот и результат:

Отец Филд: Отец Струна

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

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