Generics use type erasure. That basically means that generics are nothing more than implicit casts so when you do:
List<Integer> ...
it's no different to a normal List
and may contain Integer
s or anything really. You're just telling Java to cast get()
to an Integer
(and other things). The type simply isn't retained at runtime (mostly).
Arrays are different. Arrays are what's called covariant. That means their type is retained at runtime. So you can do:
List<Integer> list1 = new ArrayList<Integer>();
list2 = (List<String>)list1;
list2.add("hello");
which is perfectly legal and will compile and run. But:
Integer[] arr1 = new Integer[10];
String[] arr2 = (String[])arr1; // compiler error
But it gets more subtle than that too.
Integer[] arr1 = new Integer[10];
Object[] arr2 = (Object[])arr1;
arr2[5] = "hello"; // runtime error!
As to your function. When you write:
public static <T> T f(T x) {
Integer[] arr = new Integer[4];
T ret = (T) arr[2];
return ret;
}
you're telling the compiler to derive T
, being the argument type and the return type, from the argument. So when you pass in an Integer
the return type is Integer
. When you call:
Integer i = f(new Integer(4));
the compiler is simply following your instructions. The function does take and return an Object
in compiled form but it just does this:
Integer i = (Integer)f(new Integer(4));
implicitly.
Just like the List
example above there is nothing stopping you have f()
return anything you like rather than what it's supposed to return based on the parameterized type.