Разница для <? super/extends String> в описании метода и переменной
Дано:
import java.util.*;
public class Hancock {
//insert code here
list.add("foo");
}
}
Какие два фрагмента кода, вставленные независимо в строке 5, будут компилироваться без предупреждений? (Выберите два)
A. public void addString(List list) {
B. public void addString(List<String> list) {
C. public void addString(List<? super String> list) {
D. public void addString(List<? extends String> list) {
Правильными ответами являются B и C.
Ответы A и B для меня совершенно ясны. Для ответов C и D я знаю, каким образом происходит наследование, однако я не могу понять, почему ответ D не компилируется в Eclipse, пока все остальные (A с warrning о generic, B и C без warrings).
Ошибка в Eclipse для ответа D The method add(capture#1-of ? extends String) in the type List<capture#1-of ? extends String> is not applicable for the arguments (String)
.
С другой стороны, это компиляция:
public void addString() {
List<? extends String> list1 = new ArrayList<String>();
List<? super String> list2 = new ArrayList<String>();
}
Почему? Почему <? super String>
не компилируется в объявлении метода, пока он компилируется в объявлении переменной.
Я знаю, что String
является окончательным классом и не может быть расширен каким-либо другим классом, но это не объясняет мне, что здесь происходит.
Ответы
Ответ 1
Сначала давайте посмотрим ответ C:
public void addString(List<? super String> list) {
list.add("foo");
}
В объявлении этого метода указано, что вам будет разрешено передавать объекты List
, которые параметризуются каким-то супер классом String
, например String
или Object
. Итак:
- Если вы пройдете
List<String>
, list.add("foo")
будет абсолютно корректным.
- Если вы пройдете
List<Object>
, то list.add("foo")
будет абсолютно корректным, потому что "foo" - это String
(и вы можете добавить String
в List<Object>
).
Это означает, что правильный ответ C.
Теперь посмотрим ответ D.
Если у вас есть объявление метода, например:
public void addString(List<? extends String> list) {
}
это означает, что вы сможете передать List
объекты, параметризованные подтипом неизвестного String
. Итак, когда вы выполняете list.add("foo");
, компилятор не будет знать, имеет ли предоставленный объект тип, который соответствует неизвестному подтипу String
, и поэтому вызывает ошибку времени компиляции.
Если у вас есть:
public void addString() {
List<? extends String> list1 = new ArrayList<String>();
List<? super String> list2 = new ArrayList<String>();
}
Этот фрагмент компилируется отлично, потому что list1
определяется как содержать List
объекты, которые имеют неизвестный подтип String
, включая сам String
, поэтому он действителен.
Проблема в том, что вы не сможете ничего добавить, кроме null
.
Что касается list2
, переменная может содержать объекты List
, которые параметризуются некоторым супертипом String
, включая сам String
.
Дополнительная информация:
Ответ 2
Во-первых, дженерикам все равно, что String
является окончательным. Они работают одинаково для конечных и не заключительных классов.
С учетом этого должно быть понятно, почему D не разрешено - если это так, вы можете сделать это:
void test() {
List<Integer> integers = new ArrayList<Integer>();
addADouble(integers);
int a = integers.get(0); // ????
}
void addADouble(List<? extends Number> list) {
list.add(new Double(5.0));
}
List<? extends Number>
- это "Список чего-то, что расширяет число, но вы точно не знаете, что такое" Список ". - это может быть List<Double>
или List<Integer>
, или List<Number>
, или List<YourCustomSubclassOfNumber>
, поэтому вы не можете ничего добавить к нему, потому что вы не знаете, правильный ли он.