Explicit casting from super-class to sub-class
Asked Answered
M

8

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

The assignment Dog dog = (Dog) animal; does not generate a compilation error, but at runtime it generates a ClassCastException. Why can't the compiler detect this error?

Mythologize answered 1/2, 2011 at 13:11 Comment(1)
YOU are telling the compiler to NOT detect the error.Rift
N
373

By using a cast you're essentially telling the compiler "trust me. I'm a professional, I know what I'm doing and I know that although you can't guarantee it, I'm telling you that this animal variable is definitely going to be a dog."

Since the animal isn't actually a dog (it's an animal, you could do Animal animal = new Dog(); and it'd be a dog) the VM throws an exception at runtime because you've violated that trust (you told the compiler everything would be ok and it's not!)

The compiler is a bit smarter than just blindly accepting everything, if you try and cast objects in different inheritence hierarchies (cast a Dog to a String for example) then the compiler will throw it back at you because it knows that could never possibly work.

Because you're essentially just stopping the compiler from complaining, every time you cast it's important to check that you won't cause a ClassCastException by using instanceof in an if statement (or something to that effect.)

Nerval answered 1/2, 2011 at 13:23 Comment(2)
Thanks but you need dog extend from Animal if not, not work :)Infamy
@delive Of course you do, but as per the question, Dog does extend from Animal!Nerval
T
59

Because theoretically Animal animal can be a dog:

Animal animal = new Dog();

Generally, downcasting is not a good idea. You should avoid it. If you use it, you better include a check:

if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
}
Tlingit answered 1/2, 2011 at 13:13 Comment(2)
but the following code generates compilation error Dog dog=new Animal(); (incompatible types) .but in this situation compiler identifies Animal is a super class and Dog is a subclass.so the assignment is wrong.but when we cast Dog dog = (Dog) animal; it accepts .please explain me about thisMythologize
yes, because Animal is superclass. Not every animal is a Dog, right? You can refer to classes only by their types or their supertypes. Not their subtypes.Tlingit
G
57

In order to avoid this kind of ClassCastException, if you have:

class A
class B extends A

You can define a constructor in B that takes an object of A. This way we can do the "cast" e.g.:

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
}
Gravity answered 12/2, 2012 at 21:54 Comment(0)
M
31

Elaborating the answer given by Michael Berry.

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

Here you are saying to the compiler "Trust me. I know d is really referring to a Dog object" although it's not. Remember compiler is forced to trust us when we do a downcast.

The compiler only knows about the declared reference type. The JVM at runtime knows what the object really is.

So when the JVM at the runtime figures out that the Dog d is actually referring to an Animal and not a Dog object it says. Hey... you lied to the compiler and throws a big fat ClassCastException.

So if you are downcasting you should use instanceof test to avoid screwing up.

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

Now a question comes to our mind. Why the hell compiler is allowing the downcast when eventually it is going to throw a java.lang.ClassCastException?

The answer is that all the compiler can do is verify that the two types are in the same inheritance tree, so depending on whatever code might have come before the downcast, it's possible that animal is of type dog.

The compiler must allow things that might possible work at runtime.

Consider the following code snipet:

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

However, if the compiler is sure that the cast would not possible work, compilation will fail. I.E. If you try to cast objects in different inheritance hierarchies

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

Unlike downcasting, upcasting works implicitly because when you upcast you are implicitly restricting the number of method you can invoke, as opposite to downcasting, which implies that later on, you might want to invoke a more specific method.

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

Both of the above upcast will work fine without any exception because a Dog IS-A Animal, anithing an Animal can do, a dog can do. But it's not true vica-versa.

Magellan answered 23/6, 2014 at 13:7 Comment(0)
T
3

To develop the answer of @Caumons:

Imagine one father class has many children and there is a need to add a common field into that class. If you consider the mentioned approach, you should go to each children class one by one and refactor their constructors for the new field. therefore that solution is not a promising solution in this scenario

Now take a look at this solution.

A father can receive an self object from each children. Here is a father class:

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
}

Here is our child class that should implement one of its father constructor, in this case the aforementioned constructor :

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

Now we test out application:

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

And here is the result :

Father Field is: Father String

Child Field is: Child String

Now you can easily add new fields to father class without being worried of your children codes to break;

Trochal answered 23/11, 2018 at 5:24 Comment(0)
R
1

The code generates a compilation error because your instance type is an Animal:

Animal animal=new Animal();

Downcasting is not allowed in Java for several reasons. See here for details.

Romeu answered 1/2, 2011 at 13:22 Comment(1)
There is no compilation error, that's the reason for his questionEndow
G
1

As explained, it is not possible. If you want to use a method of the subclass, evaluate the possibility to add the method to the superclass (may be empty) and call from the subclasses getting the behaviour you want (subclass) thanks to polymorphism. So when you call d.method() the call will succeed withoug casting, but in case the object will be not a dog, there will not be a problem

Gman answered 18/2, 2015 at 14:57 Comment(0)
M
1

As it was said before, you can't cast from superclass to subclass unless your object was instantiated from the subclass in the first place.

However, there are workarounds.

All you need is a set of constructors and a convenience method that will either cast your object to Dog, or return a new Dog object with the same Animal properties.

Below is an example that does just that:

public class Animal {

    public Animal() {}

    public Animal(Animal in) {
        // Assign animal properties
    }

    public Dog toDog() {
        if (this instanceof Dog)
            return (Dog) this;
        return new Dog(this);
    }

}

public class Dog extends Animal {

    public Dog(Animal in) {
        super(in);
    }

    public void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = animal.toDog();
    }

}
Mohler answered 24/3, 2022 at 11:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.