Your cast method does an unchecked conversion, which is handled specially in the JVM to maintain backward compatibility with non-generic code.
Such calls cannot be shown to be statically safe under the type system using generics. Rejecting such calls would invalidate large bodies of existing code, and prevent them from using newer versions of the libraries. JLS 5.1.9
Calling the method without explicit type parameters will cause the compiler to infer the type parameter of the invocations, in this case based on their expected return type. Type Inference, JLS 15.12.2.7.. This means that code is equvivalent to this:
String foo = Caster.<String>cast("hi"); // no exception
int bar = Caster.<Integer>cast("1"); // runtime ClassCastException
Primitive types will inferred to their boxed version:
If A is a primitive type, then A is converted to a reference type U via boxing conversion and this algorithm is applied recursively to the constraint U << F. JLS 15.12.2.7.
The JVM ensures type safety by doing runtime type checks on return values of functions containing unchecked casts, at the first point where the type information is not erased (I didn't find it explicitly stated in the specification, but things look to work this way, although it is mentioned in The Java Tutorials). In this case, where you try to assign the value to a typed local variable, the type of the return value is checked, resulting in a ClassCastException
.
To give some more idea when that enforced runtime type checking cast happens, here are a few more examples:
Object a3 = Caster.<String>cast(3); // no exception, a3 is now Integer
Object a4 = (String)Caster.<String>cast(3); // an explicit cast causes runtime ClassCastException
EDIT:
Here is a StackOverflow question about when are runtime type checks enforced: When is generic return value of function casted after type erasure?