The internal implementation is exactly the same. In fact, both methods compiled with javac
will yield equal method byte code, if they compile at all).
However, during compilation, the first method is specified to not care about the component type of the list, while the second requires that the component type be invariant. This means that whenever I call such a method, the component type of list
will be fixed by whatever the call site uses.
I can call my method with a List<String>
and T
will be synonymous to String
during the call (from the perspective of the compiler). I can also call it with a List<Runnable>
and T
will be synonymous to Runnable
during the call.
Note that your method does not return anything, but it very well could do so depending on the arguments. Consider the method:
<T> T findFirst(Collection<T> ts, Predicate<T> p) { … }
You can use this method for each T
. BUT it only works if our T
is equal for the collection and predicate — this is what "invariance" means. You could in fact specify the method to be applicable in more contexts:
<T> T findFirst(Collection<? extends T> ts, Predicate<? super T> p) { … }
This method would work the same as above, but it would be more lenient in what types it accepts. Consider a type hierarchy A extends B extends C
. Then you could call:
Collection<A> cs = …;
Predicate<C> p = …;
B b = findFirst(cs, p);
We call the type of ts
covariant and the type of p
(in the method signature) contravariant.
Wildcards (?
) are a different matter. They can be bounded (like in our cases above) to be co- or contravariant. If they are unbounded, the compiler actually needs a concrete type to fill in at compile time (which is why you will sometimes get errors like "type wildcard-#15 is not a match for wildcard-#17"). The specific rules are laid out in the Java Language Specification, Section 4.5.1.