I've been reading about generic methods, and thought I understood how the generic type argument would constrain the method parameter types, but when I tested out some of the ideas with actual code, I got unexpected results. Here is a simple generic method that I don't understand:

I would have thought that this generic method was constrained so that the two arrays had to be of the same type T, but in practice the code above compiles just fine, even though one array is of type String and the other is of type Integer. When the program runs, it generates a run-time exception (ArrayStoreException).

I'm not a java expert but I'm guessing the parser can't tell that foo() accepts 2 type T[]s that have to be the same. It sees String[] is a type T[] and an Integer[] is also a type T[]. Which exactly should it use as T?
–
FalmarriNov 9 '10 at 22:02

Good answers all 'round. Well done SO'ers.
–
CurtainDogNov 10 '10 at 1:07

@CurtainDog Many thanks we do our best ;-)
–
Gary RoweNov 10 '10 at 9:13

A not-so-well-known fact about Java is that String[] is a subtype of Object[] (in contrast to List<String> not being a subtype of List<Object>). Hence the compiler can infer T = Object, making the method signature

foo(Object[] t1, Object[] t2)

which can be called with foo(stringArray, integerArray).

If you try the same with Lists:

<T> void foo(List<T> t1, List<T> t2) { ... }

you will find that

foo(new ArrayList<String>(), new ArrayList<Integer>())

doesn't compile - because there is no T such that both List<String> and List<Integer> are subtypes of List<T>. However, if instead you used wildcard types to declare the method:

void foo(List<?> t1, List<?> t2) { ... }

the method could be invoked (but the method body won't compile, because the compiler knows that the ? may refer to incompatible types).

Covariance of arrays is surprising to some - after all it violates Liskov's substitution priciple (neccessitating the existence of a runtime check that throws ArrayStoreException when it fails) and is in stark contrast to the invariance of generic types.
–
meritonNov 10 '10 at 18:01

Arrays in the Java language are
covariant -- which means that if
Integer extends Number (which it
does), then not only is an Integer
also a Number, but an Integer[] is
also a Number[], and you are free to
pass or assign an Integer[] where a
Number[] is called for. (More
formally, if Number is a supertype of
Integer, then Number[] is a supertype
of Integer[].) You might think the
same is true of generic types as well
-- that List is a supertype of List, and that you can pass a
List where a List is
expected. Unfortunately, it doesn't
work that way.

It turns out there's a good reason it
doesn't work that way: It would break
the type safety generics were supposed
to provide. Imagine you could assign a
List to a List. Then
the following code would allow you to
put something that wasn't an Integer
into a List:

Because ln is a List, adding a Float to it seems perfectly legal. But if ln were aliased with li, then it would break the type-safety promise implicit in the definition of li -- that it is a list of integers, which is why generic types cannot be covariant.