What is the difference between upcasting and downcasting, and how do you do each one safely?
Upcasting is converting a reference to a more general type, and it happens implicitly. Assigning a Circle to a Shape variable is allowed without a cast, because every Circle really is a Shape. The compiler guarantees it, so there is nothing to check.
public class Main {
public static void main(String[] args) {
Shape s = new Circle(2.0);
System.out.println(s.getClass().getSimpleName());
Circle c = (Circle) s;
System.out.println(c.radius());
if (s instanceof Square) {
Square sq = (Square) s;
System.out.println(sq.side());
} else {
System.out.println("not a square");
}
}
}
class Shape { }
class Circle extends Shape {
private final double r;
Circle(double r) { this.r = r; }
double radius() { return r; }
}
class Square extends Shape {
private final double side;
Square(double side) { this.side = side; }
double side() { return side; }
}Downcasting is the reverse, going from the general type back to the specific one, and it has to be explicit. The cast in the example is checked at run time: if the object turns out not to be a Circle, the program throws a class cast exception. The instanceof check before the second cast is the standard way to make the narrowing safe, and the output shows the branch that did not match.
The deeper point is that upcasting is how polymorphism is expressed. You program against the general type, the runtime holds a specific object, and the general type's methods dispatch to the real implementation. The general type is the contract, the specific type is the reality, and the cast is an admission that the code sometimes has to look at the reality directly.
The design advice is to minimise the number of casts. A program full of downcasts is a program that has lost track of what it is holding, which usually means the type hierarchy is not expressing the domain well. Where a cast seems unavoidable, the better move is usually to add a method to the parent type, or to model the variation as an interface so the caller never needs to narrow.