В Java, что подстановочный знак может сделать то, что обычные дженерики не могут сделать?
Я новичок в Java. В этом документе они приводят это как пример использования подстановочного знака:
static void printCollection(Collection c) {
Iterator i = c.iterator();
for (int k = 0; k < c.size(); k++) {
System.out.println(i.next());
}
}
Это их решение:
static void printCollection(Collection<?> c) {
for (Object e : c) {
System.out.println(e);
}
}
Но я мог бы сделать то же самое без джокера:
static <T> void printCollection(Collection<T> c) {
Iterator i = c.iterator();
for (int k = 0; k < c.size(); k++) {
System.out.println(i.next());
}
}
Может ли кто-нибудь показать мне простой случай использования, когда обычные генерики не будут работать, а подстановочный знак будет работать?
Обновление: ответы здесь Когда использовать шаблоны в Java Generics? НЕ говорите нам о необходимости подстановочного знака. На самом деле все наоборот.
3 ответа
Подстановочные знаки позволяют нам объявлять типы, не зависящие от конкретного параметра типа, например, "список любого вида":
List<List<?>> listOfAnyList = ...;
listOfAnyList.add( new ArrayList<String>() );
listOfAnyList.add( new ArrayList<Double>() );
Это невозможно без подстановочного знака:* потому что списки элементов могут иметь разные типы друг от друга.
И если мы попытаемся захватить это, мы обнаружим, что мы не можем:
static <E> void m(List<List<E>> listOfParticularList) {}
m( listOfAnyList ); // <- this won't compile
Еще одна вещь, подстановочные знаки позволяют нам делать то, что параметры типа не могут установить, это устанавливать нижнюю границу. (Параметр типа может быть объявлен с extends
связаны, но не super
оценка.**)
class Protector {
private String secretMessage = "abc";
void pass(Consumer<? super String> consumer) {
consumer.accept( secretMessage );
}
}
предполагать pass
вместо этого было объявлено принять Consumer<String>
, Теперь предположим, что у нас был Consumer<Object>
:
class CollectorOfAnything implements Consumer<Object> {
private List<Object> myCollection = new ArrayList<>();
@Override
public void accept(Object anything) {
myCollection.add( anything );
}
}
Проблема в том, что мы не можем передать это методу, принимающему Consumer<String>
, декларирование Consumer<? super String>
означает, что мы можем передать любой потребитель, который принимает String
, (Также см. Java Generics: Что такое PECS?.)
В большинстве случаев подстановочные знаки позволяют нам делать аккуратные заявления.
Если нам не нужно использовать тип, нам не нужно объявлять параметр типа для него.
* Технически также возможно с необработанным типом, но необработанные типы не рекомендуется.
** Я не знаю, почему Java не позволяет super
для параметра типа. 4.5.1. Аргументы типа параметризованных типов могут указывать на то, что это связано с ограничением вывода типов:
В отличие от переменных обычного типа, объявленных в сигнатуре метода, при использовании подстановочного знака не требуется никакого вывода типа. Следовательно, допустимо объявлять нижние границы подстановочным знаком […].
T обозначает универсальный тип этой структуры данных. В последнем примере вы не используете его, и он НЕ является фактическим типом (например, String), и поскольку вы его не используете, в данном случае это не имеет большого значения.
Например, если у вас была коллекция и вы пытались передать ее методу, который принимает коллекцию, это работает, потому что в пути к классам нет типа T, поэтому он считается переменной. Если вы попытались передать ту же коллекцию в метод, который принимает коллекцию, это не сработало бы, потому что у вас есть String в вашем пути к классам, поэтому он не является переменной.
Возьмите список в качестве примера.
List<?>
может быть родительским классомList<A>
,
например,
List<B> bList = new ArrayList<>(); // B is a class defined in advance
List<?> list = bList;
ты никогда не сможешь использовать <T>
в этой ситуации.
<?>
имеет подстановочный знак.
Вот,
void foo(List<?> i) {
i.set(0, i.get(0));
}
код выше не может быть скомпилирован. Вы можете это исправить:
void foo(List<?> i) {
fooHelper(i);
}
// wildcard can be captured through type inference.
private <T> void fooHelper(List<T> l) {
l.set(0, l.get(0));
}
узнать больше, http://docs.oracle.com/javase/tutorial/java/generics/capture.html
Я могу думать только о двух в настоящее время, позже может обновить.