В чем разница между "E", "T" и "?" для дженериков Java?
Я встречаю Java-код следующим образом:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
В чем разница между всеми тремя из вышеперечисленных и что они называют этим типом деклараций классов или интерфейсов в Java?
Ответы
Ответ 1
Нет никакой разницы между двумя первыми - они просто используют разные имена для параметра типа (E
или T
).
Третий не является допустимым объявлением - ?
используется в качестве шаблона, который используется при предоставлении аргумента типа, например. List<?> foo = ...
означает, что foo
относится к списку какого-либо типа, но мы не знаем что.
Все это дженерики, которые являются довольно большой темой. Возможно, вы захотите узнать об этом через следующие ресурсы, хотя, конечно, есть более доступные:
Ответ 2
Это более условно, чем что-либо еще.
-
T
означает тип
-
E
означает элемент (List<E>
: список элементов)
-
K
- это ключ (в Map<K,V>
)
-
V
- значение (в качестве возвращаемого значения или отображаемого значения)
Они полностью взаимозаменяемы (несмотря на конфликты в той же декларации).
Ответ 3
В предыдущих ответах объясняются параметры типа (T, E и т.д.), но не объясняйте подстановочный знак "?" или различия между ними, поэтому я обращусь к этому.
Во-первых, просто чтобы быть ясным: параметры подстановки и типа не совпадают. Если параметры типа определяют некоторую переменную (например, T), которая представляет тип для области действия, подстановочный знак не имеет: подстановочный знак определяет только набор допустимых типов, которые можно использовать для общего типа. Без каких-либо ограничений (extends
или super
) подстановочный знак означает "использовать любой тип здесь".
Подстановочный знак всегда находится между угловыми скобками, и он имеет смысл только в контексте общего типа:
public void foo(List<?> listOfAnyType) {...} // pass a List of any type
никогда
public <?> ? bar(? someType) {...} // error. Must use type params here
или
public class MyGeneric ? { // error
public ? getFoo() { ... } // error
...
}
Это становится более запутанным, когда они пересекаются. Например:
List<T> fooList; // A list which will be of type T, when T is chosen.
// Requires T was defined above in this scope
List<?> barList; // A list of some type, decided elsewhere. You can do
// this anywhere, no T required.
Там много совпадений в том, что возможно с определениями методов. Следующие функционально идентичны:
public <T> void foo(List<T> listOfT) {...}
public void bar(List<?> listOfSomething) {...}
Итак, если это перекрытие, зачем использовать тот или иной? Иногда это честно просто стиль: некоторые люди говорят, что если вам не нужен параметр типа, вы должны использовать подстановочный знак, чтобы сделать код более простым и удобочитаемым. Одно из основных различий, которое я объяснил выше: type params определяют переменную типа (например, T), которую вы можете использовать в другом месте области; подстановочный знак нет. В противном случае существуют два больших различия между параметрами типа и шаблоном:
Параметры типа могут иметь несколько ограничивающих классов; подстановочный знак не может:
public class Foo <T extends Comparable<T> & Cloneable> {...}
Подстановочный знак может иметь нижние границы; тип params не может:
public void bar(List<? super Integer> list) {...}
В приведенном выше примере List<? super Integer>
определяет Integer
как нижнюю границу подстановочного знака, что означает, что тип List должен быть целым или супертипом Integer. Ограничение общего типа выходит за рамки того, что я хочу подробно рассмотреть. Короче говоря, это позволяет вам определить, какие типы могут иметь общий тип. Это позволяет полиморфно обрабатывать дженерики. Например. с:
public void foo(List<? extends Number> numbers) {...}
Вы можете передать List<Integer>
, List<Float>
, List<Byte>
и т.д. для numbers
. Без ограничения типа это не сработает - это то, как генерируются дженерики.
Наконец, здесь определение метода, которое использует подстановочный знак, чтобы сделать что-то, что я не думаю, что вы можете сделать другим способом:
public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
numberSuper.add(elem);
}
numberSuper
может быть списком числа или любым супертипом числа (например, List<Object>
), а elem
должен быть числом или любым подтипом. При всем ограничении компилятор может быть уверен, что .add()
является типичным.
Ответ 4
Тип переменной <T> может быть любым не примитивным типом, который вы указываете: любым типом класса, любым типом интерфейса, любым типом массива или даже другой переменной типа.
Наиболее часто используемые имена параметров типа:
- E - Element (широко используется структурой коллекций Java)
- K - Key
- N - номер
- T - Тип
- V - Значение
В Java 7 разрешено создавать такие экземпляры:
Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6
Ответ 5
Компилятор сделает перехват для каждого подстановочного знака (например, вопросительного знака в Списке), когда он составляет такую функцию:
foo(List<?> list) {
list.put(list.get()) // ERROR: capture and Object are not identical type.
}
Однако универсальный тип, такой как V, будет в порядке и сделает его универсальным методом:
<V>void foo(List<V> list) {
list.put(list.get())
}
Ответ 6
Наиболее часто используемые имена параметров типа:
E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types
Вы увидите, что эти имена используются в API Java SE