6

This is Collections copy method(part of it) :

public static <T> void copy(List<? super T> dst, List<? extends T> src) {
   for (int i = 0; i < src.size(); i++) {
     dst.set(i, src.get(i));
   }
}

There are 4 sample call :

List<Object> objs = Arrays.<Object>asList(2, 3.14, "four");
List<Integer> ints = Arrays.asList(5, 6);

1. Collections.copy(objs, ints);
2. Collections.<Object>copy(objs, ints);
3. Collections.<Number>copy(objs, ints);
4. Collections.<Integer>copy(objs, ints);

How above call works ?

We could also declare the method with several possible signatures

1. public static <T> void copy(List<T> dst, List<T> src)
2. public static <T> void copy(List<T> dst, List<? extends T> src)
3. public static <T> void copy(List<? super T> dst, List<T> src)
4. public static <T> void copy(List<? super T> dst, List<? extends T> src)

For the example calls above,

  • the first of these is too restrictive, as it only permits calls when the destination and source have exactly the same type. (Understood).

  • the second signature works only when the type parameter is Object

  • the third signature works only when the type parameter is Integer

  • the last signature works for all three type parameters—i.e., Object, Number, and Integer.

Please explain the second, third and last signatures too ?

The remaining three are equivalent for calls that use implicit type parameters, but differ for explicit type parameters.

What does this above statement means ?

Prateek
  • 12,014
  • 12
  • 60
  • 81

1 Answers1

6

Let's consider each of your signature one by one.

1. public static <T> void copy(List<T> dst, List<? extends T> src)

If you invoke this method without explicit type parameter, the type parameter will be inferred as Object, as you are passing List<Object> as first argument. And then List<? extends Object> can accept an Integer.

However, if you invoke with explicit type argument Number, although you can pass a List<Integer> to List<? extends Number>, the same is not true for List<Object> and List<Number>, as generics are invariant.

2. public static <T> void copy(List<? super T> dst, List<T> src)

Again for implicit type parameter, T will be inferred as Integer, as you are passing List<Integer> as 2nd argument to List<T>. And then List<Object> is a valid substitute for List<? super Integer>.

If you invoke the method with explicit type argument Number, you can pass List<Object> as 1st argument, but you can't pass List<Integer> as 2nd argument. For the same reason explained above.

3. public static <T> void copy(List<? super T> dst, List<? extends T> src)

Now, this method signature will work for any type. For whatever type parameter being inferred, dst is consumer of T instance, whereas src is producer of T instance. For e.g., if you invoke with explicit type argument Number, then List<Object> is capture convertible to List<? super Number>, similarly, List<Integer> is capture convertible to List<? extends Number>. So, both the arguments are valid substitution.

So, in all the 3 cases, compiler can correctly infer the type parameters if you don't provide one explicitly. But you should use the 4th signature here for the reason being -

  • dst is consumer of T instance, so it should use lower bounds, and
  • src is producer of T instance, so it should use upper bounds.

Related Post:

Reference:

Community
  • 1
  • 1
Rohit Jain
  • 209,639
  • 45
  • 409
  • 525
  • "But you should use the 4th signature here" Why opt for a more complicated signature when a simpler one will also always work? – newacct Sep 02 '13 at 22:31
  • @newacct. I already explained that other alternatives won't work with explicit type arguments. And you don't know whether that method is going to be called with or without explicit type argument. – Rohit Jain Sep 02 '13 at 22:33
  • It does work with explicit type arguments, if you have the *right* explicit type argument. If you pick an explicit type argument that is wrong for that signature and arguments, then that's not the signature's problem. – newacct Sep 02 '13 at 22:35
  • @newacct. Well your argument is perfectly valid. But that also depends upon what that method is going to do. Given that this one is copy method. One list is consumer and other one is producer, so it is perfectly acceptible to use lower bounds and upper bounds here. – Rohit Jain Sep 02 '13 at 22:38