2010-09-15 6 views

ответ

36

Он делает это ради безопасности. Представьте себе, если бы она работала:

List<Child> childList = new ArrayList<Child>(); 
childList.add(new Child()); 

List<? extends Parent> parentList = childList; 
parentList.set(0, new Parent()); 

Child child = childList.get(0); // No! It's not a child! Type safety is broken... 

Значение List<? extends Parent> является «список некоторого типа, который простирается Parent Мы не знаем, какой тип - это может быть List<Parent>, List<Child> или List<GrandChild>.. " Это делает его безопасным для извлечения каких-либо предметов из из List<T> API и конвертирование из T в Parent, но это не безопасно вызывать в к List<T> API преобразования из Parent в T ... потому, что преобразование может быть недействительным.

+0

Дурной пример полиморфизма, я думаю? «Ребенок» не является (всегда) «родителем». – justhalf

+0

@justhalf: 'Parent' был в вопросе и предполагает, что' Child' является естественным примером подкласса. Не имена классов, которые я выбрал бы с нуля, но достаточно понятные с точки зрения контекста, о котором идет речь. –

+0

Да, я понимаю, но я думаю, было бы лучше, если бы здесь использовался «SingleParent» или «NewParent» в качестве подкласса =) – justhalf

16
List<? super Parent> 

PECS - "Продюсер - удлиняет, Cosumer - Super". Ваш List является потребителем Parent объектов.

1

Вот мое понимание.

Предположим, что у нас есть общий тип с 2-мя методами

type L<T> 
    T get(); 
    void set(T); 

Предположим, что у нас есть супер тип P, и он имеет подтипы C1, C2 ... Cn. (Для удобства мы говорим P является подтипом самого по себе, а на самом деле один из Ci)

Теперь мы также получили п конкретных типов L<C1>, L<C2> ... L<Cn>, как если бы мы вручную написаны п типов:

type L_Ci_ 
    Ci get(); 
    void set(Ci); 

Нам не пришлось вручную писать их, вот в чем вопрос. Там нет нет отношения между этими типами

L<Ci> oi = ...; 
L<Cj> oj = oi; // doesn't compile. L<Ci> and L<Cj> are not compatible types. 

Для шаблона C++, что это конец истории. Это, в основном, макрораспределение - основано на одном классе «шаблон», оно генерирует много конкретных классов, без отношения типа между ними.

Для Java существует больше. Мы также получили тип L<? extends P>, это тип супер любого L<Ci>

L<Ci> oi = ...; 
L<? extends P> o = oi; // ok, assign subtype to supertype 

Какой метод должен существовать в L<? extends P>? Как супер тип, любой из его методов должен быть опознан подтипами. Этот метод будет работать:

type L<? extends P> 
    P get(); 

, потому что ни в одном из его подтипов L<Ci>, есть метод Ci get(), который совместит с P get() - наиважнейший методом имеет ту же сигнатуру и ковариантный типа возвращаемого значения.

Это не может работать set(), хотя - мы не можем найти тип X, так что void set(X) может быть преодолено void set(Ci) для любого Ci. Поэтому set() метод не существует в L<? extends P>.

Также есть L<? super P>, который идет в другую сторону. Он имеет set(P), но не get(). Если Si является супер-типом P, L<? super P> - это супер тип L<Si>.

type L<? super P> 
    void set(P); 

type L<Si> 
    Si get(); 
    void set(Si); 

set(Si) «переопределение» set(P) не в обычном смысле этого слова, но компилятор может видеть, что любой допустимый вызов на set(P) является действительным вызовом на set(Si)

+0

Отличное объяснение причины, по которой set() в этом контексте отклоняется. –