I am using java 8.
I recently came across this:
public class Test {
public static void main(String[] args) {
String ss = "" + (Test.<Integer>abc(2));
System.out.println(Test.<Integer>abc(2));
}
public static <T> T abc(T a) {
String s = "adsa";
return (T) s;
}
}
This does not throw a java.lang.ClassCastException. Why is that?
I always thought +
and System.out.println
calls toString
. But when I try to do that it throws an Exception as expected.
String sss = (Test.<Integer>abc(2)).toString();
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.
Generics disappear at runtime, so a cast to T
is really just a cast to Object
(which the compiler will actually just get rid of), so there's no class cast exception.
abc
is just a method which takes an object and returns an object. StringBuilder.append(Object)
is the method which is called, as can be seen from the bytecode:
...
16: invokestatic #7 // Method abc:(Ljava/lang/Object;)Ljava/lang/Object;
19: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
...
When you do
String sss = (Test.<Integer>abc(2)).toString();
Then the bytecode is
...
4: invokestatic #3 // Method abc:(Ljava/lang/Object;)Ljava/lang/Object;
7: checkcast #4 // class java/lang/Integer
10: invokevirtual #5 // Method java/lang/Integer.toString:()Ljava/lang/String;
...
Your code fails at the checkcast
operation, which was not present before.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With