Ответ 1
Дженерики не ковариантны. Например:
List<Integer> l1;
List<Number> l2;
l1 = l2; // incompatible types
l2 = l1; // also incompatible
Однако подстановочные типы предлагают способ выражения ковариации:
List<? extends Integer> l1;
List<? extends Number> l2 = l1; //legal
l1
выражается как List
некоторого неизвестного типа, который является или расширяет Integer
. Аналогично, l2
является List
некоторого типа, который является или продолжается Number
. Поскольку Integer
extends Number
, компилятор знает, что назначение l1
на l2
должно быть в порядке.
Эта ситуация другая:
<S extends Number, U extends Integer> void someMethod() {
List<U> l1;
List<S> l2 = l1; //incompatible types
}
S
и U
являются параметрами типа, что означает, что им предоставляются некоторые конкретные аргументы типа вызывающими абонентами someMethod
(или введите inferrence). Эти аргументы типа могут быть конкретным типом типа Integer
или подстановочным знаком.
Пока они также ограничены, это отличается от использования ограниченных подстановочных знаков, как указано выше. Параметры типа ограничены при объявлении - внутри тела метода они не могут меняться. Например, допустим, что оба S
и U
были разрешены к Integer
, вызвав:
this.<Integer, Integer>someMethod();
В этом случае мы можем представить, что тело метода выглядит следующим образом:
List<Integer> l1;
List<Integer> l2 = l1; // okay why not?
Это было бы законно, но нам просто повезло. Есть много ситуаций, когда этого не будет. Например:
this.<Double, Integer>someMethod();
Теперь мы переустановим тело метода:
List<Integer> l1;
List<Double> l2 = l1; // danger!
Итак, вы можете видеть, что параметр ограниченного типа является чем-то большим, чем ограниченный подстановочный знак, который позволяет ковариантно "обмениваться" разными родовыми типами:
List<Integer> l1;
List<Double> l2;
List<? extends Number> l3;
l3 = l1;
l3 = l2;