Когда использовать подстановочные знаки в Java Generics?
Это из HeadFirst Java: (стр. 575)
Это:
public <T extends Animal> void takeThing(ArrayList<T> list)
Делает то же самое, что и это:
public void takeThing(ArrayList<? extends Animal> list)
Итак, вот мой вопрос: если они точно такие же, почему бы нам не написать
public <? extends Animal> void takeThing(ArrayList<?> list)
или же
public void takeThing(ArrayList<T extends Animal> list)
Кроме того, когда было бы полезно использовать? вместо T в объявлении метода (как выше) с Generics или для объявления класса? Каковы преимущества?
6 ответов
Большая разница между
public <T extends Animal> void takeThing(ArrayList<T> list)
а также
public void takeThing(ArrayList<? extends Animal> list)
в том, что в первом методе вы можете ссылаться на "T" внутри метода как на конкретный класс, который был задан. Во втором методе вы не можете сделать это.
Вот более сложный пример, чтобы проиллюстрировать это:
// here i can return the concrete type that was passed in
public <T extends Animal> Map<T, String> getNamesMap(ArrayList<T> list) {
Map<T, String> names = new HashMap<T, String>();
for (T animal : list) {
names.put(animal, animal.getName()); // i assume there is a getName method
}
return names;
}
// here i have to use general Animal
public Map<Animal, String> getNamesMap(ArrayList<? extends Animal> list) {
Map<Animal, String> names = new HashMap<Animal, String>();
for (Animal animal : list) {
names.put(animal, animal.getName()); // i assume there is a getName method
}
return names;
}
При первом способе передачи списка кошек вы получаете карту с ключом Cat. Второй метод всегда возвращает карту с общим ключом Animal.
Кстати, это не правильный синтаксис Java:
public <? extends Animal> void takeThing(ArrayList<?> list)
Используя эту форму объявления общего метода, вы должны использовать действительный идентификатор Java, а не "?".
Редактировать:
Форма "? Extends Type" применяется только к объявлению типа переменной или параметра. В объявлении обобщенного метода это должен быть "Идентификатор расширяет тип", так как вы можете ссылаться на "Идентификатор" из своего метода.
Подстановочные знаки - это взаимная / противодействующая дисперсия дженериков. Я постараюсь прояснить, что это значит, приведя несколько примеров.
В основном это связано с тем, что для типов S и T, где S является подтипом T, общий тип G<S>
не является допустимым подтипом G<T>
List<Number> someNumbers = new ArrayList<Long>(); // compile error
Вы можете исправить это с помощью диких карт
List<? extends Number> someNumbers = new ArrayList<Long>(); // this works
Обратите внимание, что вы не можете ничего положить в такой список
someNumbers.add(2L); //compile error
даже (и более удивительно для многих разработчиков):
List<? extends Long> someLongs = new ArrayList<Long>();
someLongs.add(2L); // compile error !!!
Я думаю, что SO не подходящее место, чтобы обсудить это подробно. Я постараюсь найти некоторые статьи и статьи, которые объясняют это более подробно.
Привязка типа к параметру типа может быть более мощной, в зависимости от того, что метод должен делать. Я не уверен что takeThing
должен делать, но представьте себе, что в общем у нас есть метод с одной из этих сигнатур типа:
public <T extends Animal> void foo(ArrayList<T> list);
//or
public void foo(ArrayList<? extends Animal> list);
Вот конкретный пример того, что вы можете сделать только с подписью первого типа:
public <T extends Animal> void foo(ArrayList<T> list) {
list.add(list.remove(0)); // (cycle front element to the back)
}
В этом случае T
Требуется сообщить контролеру типов, что удаляемый элемент из списка является элементом OK для добавления в список.
Вы не могли бы сделать это с подстановочным знаком, потому что, поскольку подстановочный знак не был привязан к параметру типа, его контекст не отслеживается (ну, он отслеживается через "захваты", но он не доступен для использования). Вы можете получить больше информации об этом в другом ответе, который я дал: Как работают дженерики дженериков?
Если ты пишешь ? extends T
Вы говорите "все, что является Т или более конкретным". Например: List<Shape>
может иметь только Shape
в нем, а List<? extends Shape>
могу иметь Shape
s, Circle
s, Rectangle
с и т. д.
Если ты пишешь ? super T
Вы говорите "все, что является T или более общим". Это менее часто используется, но есть свои варианты использования. Типичным примером будет обратный вызов: если вы хотите передать Rectangle
вернуться к обратному вызову, вы можете использовать Callback<? super Rectangle>
, так как Callback<Shape>
сможет справиться Rectangle
также.
Если твой takeThing
Метод должен добавить элементы в list
Параметр, версия с подстановочными символами не будет компилироваться.
Интересный случай, когда вы не добавляете в список, и обе версии, кажется, компилируются и работают.
В этом случае вы должны написать версию с подстановочными знаками, если вы хотите разрешить использование другого типа животных в списке (большая гибкость), и версию параметра, когда вам требуется фиксированный тип животных в списке: тип T.
Например, java.util.Collection
заявляет:
interface Collection<E> {
...
public boolean containsAll(Collection<?> c);
...
}
И предположим, у вас есть следующий код:
Collection<Object> c = Arrays.<Object>asList(1, 2);
Collection<Integer> i = Arrays.<Integer>asList(1, 2, 3);
i.containsAll(c); //compiles and return true as expected
Если java.util.Collection
было бы:
interface Collection<E> {
...
public boolean containsAll(Collection<E> c);
...
}
Приведенный выше тестовый код не будет компилироваться и гибкость Collection
API будет сокращен.
Стоит отметить, что последнее определение containsAll
имеет преимущество перехвата большего количества ошибок во время компиляции, например:
Collection<String> c = Arrays.asList("1", "2");
Collection<Integer> i = Arrays.asList(1, 2, 3);
i.containsAll(c); //does not compile, the integer collection can't contain strings
Но пропускает действительный тест с Collection<Object> c = Arrays.<Object>asList(1, 2);
Использование шаблонов Java Generics Wildcards регулируется принципом GET-PUT (который также известен как принцип IN-OUT). Это означает, что: используйте подстановочный знак "extends", когда вы только получаете значения из структуры, используйте "супер" подстановочный знак, когда вы только помещаете значения в структуру, и не используйте подстановочные знаки, когда вы используете оба. Это не относится к типу возврата метода. Не используйте подстановочный знак в качестве типа возврата. Смотрите пример ниже:
public static<T> void copyContainerDataValues(Container<? extends T> source, Container<? super T> destinationtion){
destination.put(source.get());
}