In most situations, an explicit upcast is entirely unnecessary and has no effect.
In your example, the explicit upcast
Animal a = (Animal)d;
could be replaced with this:
Animal a = d; // implicit upcast
The purpose of an implicit upcast (for a Java object type) is to "forget" static type information so that an object with a specific type can be used in a situation that requires a more general type. This affects compile-time type checking and overload resolution, but not run-time behavior.
(For a primitive type, an upcast results in a conversion, and can in some cases result in loss of precision; e.g. long
-> float
.)
However, there are situations where the presence of an explicit upcast changes the meaning of the statement / expression.
One situation where it is necessary to use upcasting in Java is when you want to force a specific method overload to be used; e.g. suppose that we have overloaded methods:
public void doIt(Object o)...
public void doIt(String s)...
If I have a String and I want to call the first overload rather than the second, I have to do this:
String arg = ...
doIt((Object) arg);
A related case is:
doIt((Object) null);
where the code won't compile without the type cast. I'm not sure if this counts as an upcast (see JLS 5.1.13 last paragraph) but it should be mentioned anyway.
A second situation involves varadic parameters:
public void doIt(Object... args)...
Object[] foo = ...
doIt(foo); // passes foo as the argument array
doIt((Object) foo); // passes new Object[]{foo} as the argument array.
A third situation is when performing operations on primitive numeric types; e.g.
int i1 = ...
int i2 = ...
long res = i1 + i2; // 32 bit signed arithmetic ... might overflow
long res2 = ((long) i1) + i2; // 64 bit signed arithmetic ... won't overflow