Нужны ли генераторы подстановочных знаков?
например:
public String add(Set<?> t){
...;
}
public <T> String add(Set<T> t){
...;
}
Первый использует подстановочные дженерики; вторая - обычная форма общего метода.
Какая разница?
В какой ситуации нам нужны подстановочные дженерики, а не нормальная форма дженериков?
Ответы
Ответ 1
Вот ситуация, когда требуются подстановочные знаки. Этот метод принимает List<List<?>>
, который является списком списков. Метод может добавлять в него списки различных типов компонентов:
public void foo(List<List<?>> t) {
t.add(new ArrayList<String>());
t.add(new ArrayList<Integer>());
}
Вы не можете сделать это, используя общие параметры типа без подстановочных знаков. Например, следующее не работает:
public <T> void foo(List<List<T>> t) {
t.add(new ArrayList<String>()); // does not compile
t.add(new ArrayList<Integer>()); // does not compile
}
Ответ 2
Так как поддержка дженериков была добавлена, использование параметризованного типа без предоставления параметра типа обычно вызывает предупреждение компилятора. С другой стороны, бывают ситуации, когда вам все равно, что такое параметр типа (т.е. Вы не используете тип где-либо), или, что еще хуже, вы, возможно, не знаете, что T
вообще, и используя <?>
, вы можете просто выразить это, не вызывая предупреждения компилятора.
Возможный вариант использования для случая "не заботьтесь" (очень просто для краткости, но вы понимаете):
public void clearList(List<?> list) {
list.clear();
}
Пример для случая "не знаю": фактическая подпись метода из класса Class
:
static Class<?> forName(String className);
Здесь метод возвращает объект некоторого типа Class
. Class
является общим, но, конечно, вы не знаете тип, потому что он зависит от параметра className
, который разрешен во время выполнения. Таким образом, вы не можете поместить T
здесь, так как T
не известно во время компиляции (даже для определенного сайта вызова), а использование только Class
без параметра типа будет плохой практикой и вызовет предупреждение компилятора.
Ответ 3
Подстановочная форма - это когда вы не возражаете против того, какие типы объектов вы обрабатываете.
Форма generics позволяет добавлять ограничения на тип обработанных объектов.
Пример использования может быть следующим:
общий репозиторий с методами add/update/remove, вы определяете общее поведение с использованием общего типа:
public class Repository<T>{
public void add(T t){...}
public void update(T t){...}
public void remove(T t){...}
}
Затем, чтобы создать репозиторий для Apple
и Banana
, вы просто расширяете этот класс и заменяете T действительным типом:
public class AppleRepo extends Repository<Apple> {}
public class BananaRepo extends Repository<Banana> {}
Если общий репозиторий был объявлен как Repository<?>
, это не было бы хорошо, потому что оно не ограничивается бананом, и вы не сможете использовать в нем Banana
определенные методы без кастования объектов;
Также генерические средства позволяют вам выражать дополнительные ограничения, например
Repository<T extends Fruit>
позволяет ограничить общий класс репозитория плодами. И вы сможете совершать вызовы методов Fruit
в своем коде.
Ответ 4
Там нет разницы в вызове методов.
Во втором методе (add(Set<T>)
) вы можете создавать переменные типа T
:
public <T> String add(Set<T> t){
T item = t.iterator().next();
//....
}
Это дает вам дополнительную проверку типов.
В первом методе вы остаетесь с помощью Object
.