2016-08-18 6 views
17

Я считаю, что тип ? в generics - это определенного неизвестного типа. Это означает, что объявление, например, списка этого типа, помешает нам добавить в него какой-либо объект.Неравномерности с (?) Подстановочным знаком общего типа

List<?> unknownList; 
unknownList.add(new Object()); // This is an error. 

Компилятор выдает сообщение об ошибке, как ожидалось.

Но когда неизвестный тип является генератором второго уровня, компилятор, похоже, не заботится.

class First<T> {} 

List<First<?>> firstUnknownList; 

// All these three work fine for some reason. 
firstUnknownList.add(new First<>()); 
firstUnknownList.add(new First<Integer>()); 
firstUnknownList.add(new First<String>()); 

Я думал, что, вероятно, компилятор не заботится о родовом параметре второго уровня на всех, но это не тот случай,

List<First<Integer>> firstIntegerList; 
firstIntegerList.add(new First<String>()); // This gives a compiler error as expected. 

Итак, почему компилятор позволяет добавлять любой элемента, когда во втором примере допустим только неизвестный элемент (и, следовательно, ничего)?

Примечание: Компилятор Java 1.8

+0

Добавив «новый первый ()' в список «>», что вы можете сделать, это небезопасно? Представьте, что у вас есть производитель '' и метод '' потребителя '' на 'First ': вы не можете вызвать пользователя; и вы можете получить «объект» от производителя. Таким образом, в этом нет ничего небезопасного - если у меня не будет очевидного случая. –

+3

"и, следовательно, ничего" кроме "null". –

+0

«Список » может быть присвоен «List », а затем добавление объекта * * неверно. 'Список ' будет делать. –

ответ

13

Вы можете добавить что-нибудь к List<T>, что вы можете хранить в ссылке типа T:

T item = ... 
List<T> list = new ArrayList<>(); 
list.add(item); 

First<?> является супертипом First<T>; так что вы можете сохранить ссылку на First<T> в переменной типа First<?>:

First<?> first = new First<String>(); 

Таким образом, подставляя T для First<?> выше:

First<?> item = new First<String>(); 
List<First<?>> list = new ArrayList<>(); 
list.add(item); 

Все, что происходит в примере OP является то, что временная переменная item опущено:

firstUnknownList.add(new First<String>()); 

Однако, если вы делаете это с firstIntegerList пример:

First<Integer> item = new First<String>(); // Compiler error. 
List<First<Integer>> list = new ArrayList<>(); 
list.add(item); 

ясно, почему это не допускается: вы не можете сделать назначение item.


Это также можно видеть, что вы не можете ничего небезопасно делать с содержимым этого списка.

Если добавить пару методов в интерфейсе:

interface First<T> { 
    T producer(); 
    void consumer(T in); 
} 

Теперь рассмотрим, что вы можете сделать с элементами, которые вы добавили в список:

for (First<?> first : firstUnknownList) { 
    // OK at compile time; OK at runtime unless the method throws an exception. 
    Object obj = first.producer(); 

    // OK at compile time; may fail at runtime if null is not an acceptable parameter. 
    first.consumer(null); 

    // Compiler error - you can't have a reference to a ?. 
    first.consumer(/* some maybe non-null value */); 
} 

так что ISN» Фактически все, что вы действительно можете сделать с элементами этого списка, которые будут нарушать безопасность типов (при условии, что вы не сделаете ничего намеренного, чтобы нарушить его, например, используя необработанные типы). Вы можете продемонстрировать, что общие методы производителя/потребителя аналогичным образом защищены или запрещены компилятором.

Так что нет причин не, чтобы вы могли это сделать.

+0

Получил это ... Не понял «Что-то » - это супертип определенных родовых типов. Благодаря... – Codebender

4

я изменю First интерфейс для Box интерфейса

Box<?> uknownBox серого ящика с чем-то в нем

Box<Apple> appleBox коробки с яблоком

List<Box<Apple>> appleBoxList много коробка с яблоками

List<Box<?>> uknownBoxList много неизвестных серых коробков

appleBoxList.add(new Box<Orange>()) - не может добавить ящик с апельсинами в список яблочных коробки

unknownBoxList.add(new Box<?>()) - мы не знаем, что в том, что серые коробки, добавив еще одну неизвестную серую коробку, ничего не меняет

unknownBoxList.add(new Box<Orange>()) - same for concrete boxes 

unknownBoxList.add(new Box<Apple>()) - since you are not allowed to 'open' them 

unknownBoxList = appleBoxList это не компилируется, чтобы предотвратить добавление серых (возможно, не яблочных) ящиков в список ящиков. из-за того, что предыдущая операция является законной.

4

Все дело в подтипах/супертипах.

List<?> - это список, содержащий элементы неизвестного (но конкретного) типа. Вы никогда не знаете, какой тип точно содержится в этом списке. Таким образом, вы не можете добавлять объекты к нему, потому что они могут быть неправильного типа:

List<Integer> ints = new ArrayList<Integer>(); 
List<?> unknowns = ints; 

// It this worked, the list would contain a String.... 
unknowns.add("String"); 

// ... and this would crash with some ClassCastException 
Integer i = ints.get(0); 

Это также может быть ясно, что вы можете сделать

List<Number> numbers = null; 
Integer integer = null; 
numbers.add(integer); 

Это работает, потому что Number является истинный супертип Integer. Он не нарушает безопасность типа, чтобы добавить в список объект более определенного типа.


Ключевым моментом в отношении второго примера:

First<?> является супертипом каждого First<T>

Вы можете всегда к чему-то вроде

First<Integer> fInt = null; 
First<Integer> fString = null; 

First<?> f = null; 
f = fInt; // works 
f = fString; // works 

Так причина почему вы можете добавить First<String> в List<First<?>> так же, как и w hy вы можете добавить Integer в List<Number>: Элемент, который вы хотите добавить, имеет значение подтипа элементов, ожидаемых в списке.

0

Я считаю, что тип? в generics - это определенный неизвестный тип.

Это немного неточно.Да, подстановочные типа стоит неизвестного типа, но может стоять для различных типов в разное время:

List<?> list = new ArrayList<String>(); 
list = new ArrayList<Integer>(); 

Единственный инвариант, что выражение типа которого содержит подстановочные всегда будет давать значение, тип которого соответствует тому, что подстановочные. Поскольку каждое значение имеет тип, который не является только подстановочным знаком, можно сказать, что подстановочный знак обозначает (более) «специфический» тип в любое время.

Смежные вопросы