How does the Java cast operator work?
Solution 1:
Is the JLS good enough?
Casting conversion is applied to the operand of a cast operator (§15.16): the type of the operand expression must be converted to the type explicitly named by the cast operator. Casting contexts allow the use of:
- an identity conversion (§5.1.1)
- a widening primitive conversion (§5.1.2)
- a narrowing primitive conversion (§5.1.3)
- a widening reference conversion (§5.1.5) optionally followed by an unchecked conversion (§5.1.9)
- a narrowing reference conversion (§5.1.6) optionally followed by an unchecked conversion
- a boxing conversion (§5.1.7)
- an unboxing conversion (§5.1.8).
Actually, maybe this part is more relevant:
The detailed rules for compile-time legality of a casting conversion of a value of compile-time reference type S to a compile-time reference type T are as follows:
- If S is a class type:
- If T is a class type, then either |S| <: |T|, or |T| <: |S|; otherwise a compile-time error occurs. Furthermore, if there exists a supertype X of T, and a supertype Y of S, such that both X and Y are provably distinct parameterized types (§4.5), and that the erasures of X and Y are the same, a compile-time error occurs.
- If T is an interface type:
- If S is not a
final
class (§8.1.1), then, if there exists a supertype X of T, and a supertype Y of S, such that both X and Y are provably distinct parameterized types, and that the erasures of X and Y are the same, a compile-time error occurs. Otherwise, the cast is always legal at compile time (because even if S does not implement T, a subclass of S might).- If S is a
final
class (§8.1.1), then S must implement T, or a compile-time error occurs.- If T is a type variable, then this algorithm is applied recursively, using the upper bound of T in place of T.
- If T is an array type, then S must be the class
Object
, or a compile-time error occurs.- If S is an interface type:
- If T is an array type, then T must implement S, or a compile-time error occurs.
- If T is a type that is not
final
(§8.1.1), then if there exists a supertype X of T, and a supertype Y of S, such that both X and Y are provably distinct parameterized types, and that the erasures of X and Y are the same, a compile-time error occurs. Otherwise, the cast is always legal at compile time (because even if T does not implement S, a subclass of T might).- If T is a type that is
final
, then:
- If S is not a parameterized type or a raw type, then T must implement S, and the cast is statically known to be correct, or a compile-time error occurs.
- Otherwise, S is either a parameterized type that is an invocation of some generic type declaration G, or a raw type corresponding to a generic type declaration G. Then there must exist a supertype X of T, such that X is an invocation of G, or a compile-time error occurs. Furthermore, if S and X are provably distinct parameterized types then a compile-time error occurs.
- If S is a type variable, then this algorithm is applied recursively, using the upper bound of S in place of S.
- If S is an array type SC[], that is, an array of components of type SC:
- If T is a class type, then if T is not
Object
, then a compile-time error occurs (becauseObject
is the only class type to which arrays can be assigned).- If T is an interface type, then a compile-time error occurs unless T is the type
java.io.Serializable
or the typeCloneable
, the only interfaces implemented by arrays.- If T is a type variable, then:
- If the upper bound of T is
Object
or the typejava.io.Serializable
or the typeCloneable
, or a type variable that S could legally be cast to by recursively applying these rules, then the cast is legal (though unchecked).- If the upper bound of T is an array type TC[], then a compile-time error occurs unless the type SC[] can be cast to TC[] by a recursive application of these compile-time rules for casting.
- Otherwise, a compile-time error occurs.
- If T is an array type TC[], that is, an array of components of type TC, then a compile-time error occurs unless one of the following is true:
- TC and SC are the same primitive type.
- TC and SC are reference types and type SC can be cast to TC by a recursive application of these compile-time rules for casting.
Perfectly clear now, isn't it? :D
In other words, this is the best I can do without knowing more details about your problem.
Solution 2:
A likely cause of class cast mystifcation is that not only do the types have to match but also they must be loaded by the same classloader.
You should be able to dump not only the type hierarchy but also the identity of the classloader for each class.
These kind of problems are not uncommon in appserver-style environments where application code and infratructure code are deliberately isolated - for example if system classes are accidentally included in applciation JARs you can have two copies of the "same" class inthe JVM and life gets confusing
Solution 3:
Casting asserts that the runtime type of an object is compatible with the given static type, and thus allows you to call methods of that type on the object.
Here obj is a Integer object, but only accessible though an Object reference:
Object obj = new Integer(1);
Casting lets you treat it as an Integer (or some superclass of Integer) again:
System.out.println(((Integer) obj).intValue());
ClassCastException occours when the static type given does not match the runtime type of the object:
System.out.println(((Float) obj).intValue()); // runtime error
You can find the runtime type of any object by using getClass() and the various Class methods:
System.out.println(obj.getClass()); // prints "class java.lang.Integer"
Solution 4:
Other useful and authoritative references are found in the Java Virtual Machine Specification, specifically §2.6.5, "Narrowing Reference Conversions", and especially the definition of the checkcast
instruction.