В чем разница между «E», «T» и «?» для дженериков Java?

261

Я сталкиваюсь с кодом Java, как это:

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}

В чем разница между всеми тремя из вышеперечисленных и как они называют этот тип объявлений класса или интерфейса в Java?

туз
источник
1
Я сомневаюсь, что есть какая-то разница. Я думаю, это просто имя для этого параметра типа. И последний ли вообще действителен?
CodesInChaos

Ответы:

231

Ну, нет никакой разницы между первыми двумя - они просто используют разные имена для параметра типа ( Eили T).

Третье не является допустимым объявлением - ?используется в качестве подстановочного знака, который используется при предоставлении аргумента типа , например, List<?> foo = ...означает, что fooотносится к списку некоторого типа, но мы не знаем, что.

Все это дженерики , и это довольно большая тема. Вы можете узнать об этом через следующие ресурсы, хотя, конечно, есть и другие:

Джон Скит
источник
1
Похоже, ссылка на PDF не работает. Я обнаружил , что , как представляется, скопировать здесь , но я не могу быть 100% уверен , так как я не знаю , что оригинал выглядел.
Джон
2
@Джон: Да, это тот. Будет ли редактировать ссылку, будь то один или Oracle ...
Джон Скит
Есть ли что-нибудь кроме T, E и? используется в дженериках? Если да, то, что они и что они значат?
sofs1
1
@ sofs1: В этом нет ничего особенного, Tи Eони просто идентификаторы. Вы могли бы написать, KeyValuePair<K, V>например. ?имеет особое значение, хотя.
Джон Скит
215

Это больше обычного, чем все остальное.

  • T предназначен для типа
  • Eдолжен быть элементом ( List<E>список элементов)
  • KКлюч (в Map<K,V>)
  • V Значение (в качестве возвращаемого значения или сопоставленного значения)

Они полностью взаимозаменяемы (несмотря на конфликты в одной и той же декларации).

чокнутый урод
источник
20
Буква между <> это просто имя. То, что вы описываете в своем ответе, является просто условностями. Это даже не должна быть одна заглавная буква; Вы можете использовать любое имя, которое вам нравится, так же, как вы можете давать классы, переменные и т. д. любое имя, которое вам нравится.
Джеспер
Более подробное и понятное описание доступно в этой статье oracle.com/technetwork/articles/java/…
fgul
6
Вы не объяснили вопросительный знак. Downvoted.
Shinzou
129

Предыдущие ответы объясняют параметры типа (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)  {...}

Итак, если есть совпадение, зачем использовать один или другой? Иногда, честно говоря, это просто стиль: некоторые люди говорят, что если вам не нужен параметр типа, вы должны использовать подстановочный знак только для того, чтобы сделать код проще / более читабельным. Одно основное отличие, которое я объяснил выше: параметры типа определяют переменную типа (например, T), которую вы можете использовать в другом месте области действия; подстановочный знак не. В противном случае между параметрами типа и подстановочным знаком есть две большие разницы:

Тип params может иметь несколько ограничивающих классов; подстановочный знак не может:

public class Foo <T extends Comparable<T> & Cloneable> {...}

Подстановочный знак может иметь нижние границы; Тип params не может:

public void bar(List<? super Integer> list) {...}

В приведенном выше List<? super Integer>описании определяется Integerкак нижняя граница подстановочного знака, означающего, что тип списка должен быть Integer или супер-тип 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()он безопасен для типов.

Соколиный Глаз Паркер
источник
"public void foo (List <? extends Number> numbers) {...}" должен "extends" быть "super"?
1a1a11a
1
Нет. Цель этого примера - показать сигнатуру, которая полиморфно поддерживает список номеров и подтипов номеров. Для этого вы используете «extends». То есть, «передайте мне список номеров или что-нибудь, что расширяет номер» (List <Integer>, List <Float>, что угодно). Подобный метод может затем выполнить итерацию по списку и выполнить для каждого элемента «e», например, e.floatValue (). Не имеет значения, какой подтип (расширение) числа вы передаете - вы всегда сможете ".floatValue ()", потому что .floatValue () является методом Number.
Соколиный Глаз Паркер
В вашем последнем примере «List <? Super Number>» может быть просто «List <Number>», поскольку метод не допускает ничего более общего.
Джессара
@jessarah Нету. Возможно, мой пример неясен, но я упоминаю в примере, что adder () может принимать List <Object> (Object является суперклассом Number). Если вы хотите, чтобы он мог это сделать, он должен иметь подпись «Список <? Super Number>». В этом и заключается смысл "супер" здесь.
Соколиный Глаз Паркер
2
Этот ответ очень хорошо объясняет различия между подстановочными знаками и параметрами типов, с этим ответом должен быть один специальный вопрос. В последнее время я углубляюсь в дженерики, и этот ответ очень помог мне собрать все вместе, вкратце, много точной информации, спасибо!
Testo
27

Переменная типа <T> может быть любым указанным вами не примитивным типом: любым типом класса, любым типом интерфейса, любым типом массива или даже переменной другого типа.

Наиболее часто используемые имена параметров типа:

  • Электронный элемент (широко используется в Java Collections Framework)
  • K - Ключ
  • N - номер
  • T - Тип
  • V - значение

В Java 7 разрешено создавать такие экземпляры:

Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6
Ува
источник
3

Наиболее часто используемые имена параметров типа:

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

Вакас Ахмед
источник
2

Компилятор сделает захват для каждого подстановочного знака (например, вопросительного знака в Списке), когда он составляет такую ​​функцию:

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())
}
Тийна
источник