Reputation: 4832
Consider:
public class Super
{
Integer state = 0;
Super f()
{
Super x = new Super();
x.state = this.state + 1;
return x;
}
}
public class Sub extends Super
{
Sub f()
{
return (Sub) super.f();
}
}
public class Main
{
public static void main (String[] args)
{
Sub s = new Sub();
Sub t = s.f();
}
}
This results in a crash:
Exception in thread "main" java.lang.ClassCastException: Super cannot be cast to Sub
at Sub.f(Sub.java:5)
at Main.main(Main.java:6)
And yet, an instance of Sub
is not in a smallest feature distinct from that of Super
.
In a more realistic example, Sub
would have exactly the same properties as Super
(which is
what makes me believe they can be safely cast both ways), but redefine some of its methods.
Is it possible to make this code work, without duplicating the definition of f
?
Upvotes: 0
Views: 100
Reputation: 15212
Let's address the first part of the question :
Is it possible to make this code work
In it's current state, the code cannot work because this is against the language specification as defined in Section 5.5.3 of the Java Language Specification which states that :
If T is a class type, then R must be either the same class (§4.3.4) as T or a subclass of T, or a run-time exception is thrown
In the example provided in the question, (return (Sub) super.f();
), T
is Sub
and R
is Super
.
Let's now address the second part of the question :
without duplicating the definition of f
One way to achieve this is to move the code that is common between super
and Sub
to a separate method :
public class Super {
Integer state = 0;
Super f() {
Super x = new Super();
incrementState(x);
return x;
}
protected void incrementState(Super x) {
x.state = this.state + 1;
}
}
public class Sub extends Super {
Sub f() {
Sub s = new Sub();
incrementState(s);
return s;
}
}
Upvotes: 1
Reputation: 929
So the short answer is that yes, you can downcast. Provided that the actual object type at runtime is the target type you are down-casting too.
When your example runs return (Sub) super.f();
it'll fail with a java.lang.ClassCastException
as in this instance the actual object is not a Sub
, it is a Super
. You can see this by modifying your method a bit as follows:
Sub f() {
Super sup = super.f();
if(sup instanceof Sub) {
System.out.println("Instance of Sub");
} else {
System.out.println("Not an instance of Sub, it is a : " + sup.getClass());
}
return (Sub) sup;
}
The reason here is because in Super f()
your instantiating a pure Super
object and returning it, hence the actual object type is Super
and since a Super
is not a Sub
so we cannot cast it to a Sub
.
Here is an example:
public class Animal {
private String type;
private int age;
public Animal(String type, int age) {
this.type = type;
this.age = age;
}
public int getAge() {
return age;
}
}
public class Cat extends Animal {
public Cat(String type, int age) {
super(type, age);
}
}
public class Dog extends Animal {
public Dog(String type, int age) {
super(type, age);
}
}
public class Vet {
public static void printHumanYears(Animal animal) {
if(animal instanceof Dog) {
Dog dog = (Dog) animal;
System.out.println("Dogs age : " + dog.getAge() * 7);
} else if(animal instanceof Cat) {
Cat cat = (Cat) animal;
System.out.println("Cat age : " + cat.getAge() * 5);
} else {
System.out.println("Not sure how to calculate age for a " + animal.getClass());
}
}
public static void main (String [] args) {
Dog dog = new Dog("Collie", 10);
Cat cat = new Cat("Tabby", 20);
Animal animal = new Animal("Animal", 20);
printHumanYears(dog);
printHumanYears(cat);
printHumanYears(animal);
}
}
When the method printHumanYears(Animal animal)
is invoked, it is passed an Animal
Object, we then check what this Animal
object is an instance of and then downcast accordingly. The reason we would not get a ClassCastException
is because the target object (either Dog
or Cat
) was create at run time. When we pass an Animal
it can't downcast to either a Dog
or Cat
because it's neither of these when it was initially instantiated.
Good to remember:
ClassCastException
, so always a good idea to use instanceof
to check the the target type is what you are trying to cast too.Upvotes: 1
Reputation: 9566
Yes, you can, but your f
operation should be protected inside class hierarchy.
static class Super implements Cloneable {
int state = 0;
// inspection
public int getState() {
return state;
}
// default behavior
public void doSomething() {
state += 1;
}
// unsafe, should be protected
public <S extends Super> S f() throws CloneNotSupportedException {
// unchecked cast !!!
S s = (S) this.clone();
s.doSomething();
return s;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
static class Sub extends Super {
// override behavior
@Override
public void doSomething() {
super.doSomething();
super.doSomething();
}
}
public static void main(String[] args) throws Exception {
Sub s = new Sub();
System.out.printf("%d => %d%n", s.getState(), s.f().getState());
}
With output:
0 => 2
Here, f
return not Super
but any S
extending Super
, now f
will be closed under the group operations (each superclass operations). But must be protected since that cast is unchecked at compile time. Eg.
Sub s = new Sub();
Sub t = s.f(); // ok
Sub2 u = s.f(); // java.lang.ClassCastException !!!!
That is why f
operation should be protected and not publicly exposed.
Upvotes: 1