В чем разница между "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