It doesn't throw a ClassCastException
because all generic type information is stripped from the compiled code (a process called type erasure). Basically, any type parameter is replaced by Object
. That's why the first version works. It's also why the code compiles at all. If you ask the compiler to warn about unchecked or unsafe operations with the -Xlint:unchecked
flag, you'll get a warning about an unchecked cast in the return
statement of abc()
.
With this statement:
String sss = (Test.<Integer>abc(2)).toString();
the story is a bit different. While the type parameter T
is replaced by Object
, the calling code gets translated into byte code that explicitly casts the result to Integer
. It is as if the code were written with a method with signature static Object abc(Object)
and the statement were written:
String sss = ((Integer) Test.abc(Integer.valueOf(2))).toString();
That is, not only does the cast inside abc()
go away due to type erasure, a new cast is inserted by the compiler in the calling code. This cast generates a ClassCastException
at run time because the object returned from abc()
is a String
, not an Integer
.
Note that the statement
String ss = "" + (Test.<Integer>abc(2));
doesn't require a cast because the compiler simply feeds the object returned by abc()
into a string concatenation operation for objects. (The details of how this is done varies with the Java compiler, but it is either a call to a StringBuilder
append method or, as of Java 9, a call to a method created by StringConcatFactory
.) The details here don't matter; the point is that the compiler is smart enough to recognize that no cast is needed.