Welcome to type erasure. At runtime, Class<T>
has been erased to Class
- to the JVM, what was in the source code a Class<Integer>
, Class<String>
, etc. all look the same. This is the meaning of an unchecked cast - the developer does it at his or her own peril, because it will not fail fast with a ClassCastException
if it's wrong. Instead, some later ClassCastException
may occur instead, at a cast that was inserted by the compiler during the type erasure process. This state, in which generically-typed references are pointing to objects they shouldn't have been allowed to, is known as heap pollution.
O.M.G. - any better way to enforce the Type Argument?
No, this is the best Java can offer in terms of generic type safety - it really is opt-in. Lazy or abusive code is free to do unchecked casts or use raw types (which bring implicit unchecked casts to anything they touch), although many IDEs offer to make these compiler errors instead of warnings.
As a side note, unchecked casts are occasionally valid, for example when implementing Joshua Bloch's Effective Java item 27, "favor generic methods":
private static final Comparator<Object> HASH_CODE_COMPARATOR =
new Comparator<Object>() {
@Override
public int compare(final Object o1, final Object o2) {
return Integer.compare(o1.hashCode(), o2.hashCode());
}
};
public static <T> Comparator<T> hashCodeComparator() {
@SuppressWarnings("unchecked") // this is safe for any T
final Comparator<T> withNarrowedType =
(Comparator<T>)(Comparator<?>)HASH_CODE_COMPARATOR;
return withNarrowedType;
}
Here, the unchecked cast is safe because HASH_CODE_COMPARATOR
behaves contravariantly. It's stateless and works for any Object
, so we can let the caller decide its generic type:
Comparator<String> c = hashCodeComparator();
In this case we can use @SuppressWarnings("unchecked")
to remove the unchecked warning, essentially telling the compiler to trust us. It's also a good idea to add an explanatory comment.