В чем разница между ‘E’, ‘T’ и ‘?’ для Java generics?

#java #generics

#java #дженерики

Вопрос:

Я сталкиваюсь с Java-кодом, подобным этому:

 public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}
  

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

Ответ №1:

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

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

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

Комментарии:

1. Похоже, что ссылка на PDF не работает. Я нашел то, что кажется копией здесь , но я не могу быть уверен на 100%, поскольку я не знаю, как выглядел оригинал.

2. @John: Да, это тот самый. Отредактирует ссылку в, будь то эта или Oracle…

3. Есть ли что-нибудь еще, кроме T, E и? используется в дженериках? Если да, то что это такое и что они означают?

4. @sofs1: В T and E нет ничего особенного — это просто идентификаторы. Вы могли бы написать KeyValuePair<K, V> , например. ? однако имеет особое значение.

5. @JonSkeet «это просто идентификаторы» , что означает, что они должны соответствовать тем же ограничениям на имена, что и имена классов, имена полей, имена методов и т.д. Например, Foo<hello_world> допустимо. Использование одной заглавной буквы является стандартом именования, который рекомендуется в спецификации языка Java : «Имена переменных типа должны быть лаконичными (по возможности из одного символа), но запоминающимися и не должны включать строчные буквы. Это позволяет легко отличать параметры типа от обычных классов и интерфейсов.»

Ответ №2:

Это скорее соглашение, чем что-либо еще.

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

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

Комментарии:

1. Буква между < > — это просто имя. То, что вы описываете в своем ответе, — это просто соглашения. Это даже не обязательно должна быть одна заглавная буква; вы можете использовать любое имя, которое вам нравится, точно так же, как вы можете присваивать классы, переменные и т.д. любое имя, которое вам нравится.

2. Более подробное и понятное описание доступно в этой статье oracle.com/technetwork/articles/java /…

3. Вы не объяснили вопросительный знак. Голос отклонен.

4. Почему это вообще ответ, в любом случае?

Ответ №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)  {...}
  

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

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

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

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

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

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

Комментарии:

1. «public void foo(список<? расширяет число> числа) {…}» должно ли «extends» быть «super»?

2. Нет. Смысл этого примера в том, чтобы показать сигнатуру, которая полиморфно поддерживает список Number и подтипы Number. Для этого вы используете «extends». Т.Е. «передайте мне список чисел или все, что расширяет число» (List<Integer>, List<Float> , что угодно). Затем подобный метод может выполнять итерацию по списку и для каждого элемента «e» выполнять, например, e.floatValue(). Не имеет значения, какой подтип (расширение) числа вы передаете — вы всегда сможете «.floatValue()», потому что . floatValue() — это метод Number.

3. Этот ответ очень, очень хорош в объяснении различий между подстановочными знаками и параметрами типа, с этим ответом должен быть один специальный вопрос. В последнее время я углубляюсь в дженерики, и этот ответ очень помог мне собрать вещи воедино, короче говоря, много точной информации, спасибо!

4. Это единственный ответ, который действительно отвечает на вопрос

5. @HawkeyeParker Спасибо за объяснение, очень подробное!

Ответ №4:

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

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

  • E — Элемент (широко используется в 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
  

Ответ №5:

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

 E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
R - Result
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types
  

Вы увидите, что эти имена используются во всем Java SE API

Ответ №6:

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

 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())
}