You are correct that "the JVM would know nothing about if T
is String
or anything else", and as a result nothing will happen at runtime.
Normally, the runtime evaluation of a (reference type) cast would involve checking whether the object that you are casting actually is of the type that you are casting to. For example:
Object o = new Object();
String s = (String)o;
At runtime, a check is performed on o
, and it will be found that o
is actually of type Object
and not String
, and as such, a ClassCastException
will be thrown.
On the other hand, if you are casting to a type parameter T
, the runtime has no idea what T
is, and so does not do any checking, hence this is an "unchecked cast", as the warning says.
So if val
isn't actually of type T
, no exceptions will be thrown:
Cup<String> c = new Cup<>();
c.get();
Even though I called get
in the above code, and the line with the cast is executed, there will be no exceptions, because there is no runtime check. The runtime thinks that get
returns an Object
. It is only when the runtime knows what type to cast, does the cast happen, and the exception get thrown:
Cup<String> c = new Cup<>();
String s = c.get(); // this throws an exception
The compiler inserts the cast in the second line like so String s = (String)c.get();
As you can see, it doesn't really matter that the runtime doesn't know what T
is at the line where the cast is, because you don't need the cast there anyway. Consider the type-erased version of your code:
public class Cup {
public Object get() {
Integer val = 1;
Object result = val;
return result;
}
public static void main(String[] args) {
Cup cup = new Cup();
System.out.println(cup.get());
}
}
You'll notice that this is perfectly fine code that will compile!
(T)val
here is mostly here to make the compiler happy, to convince the compiler that val
is indeed of type T
.