#java #generics
#java #дженерики
Вопрос:
Я сталкиваюсь с Java-кодом, подобным этому:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
В чем разница между всеми тремя из вышеперечисленных и как они называют этот тип объявлений класса или интерфейса в Java?
Ответ №1:
Ну, между первыми двумя нет никакой разницы — они просто используют разные имена для параметра типа ( E
или T
).
Третье не является допустимым объявлением — ?
используется как подстановочный знак, который используется при предоставлении аргумента типа, например, List<?> foo = ...
означает, что foo
ссылается на список некоторого типа, но мы не знаем, какого.
Все это дженерики, что является довольно обширной темой. Возможно, вы захотите узнать об этом с помощью следующих ресурсов, хотя, конечно, есть и другие доступные:
- Руководство по дженерикам Java
- Руководство по языку дженериков
- Дженерики в языке программирования Java
- Часто задаваемые вопросы по Java Generics от Анжелики Лангер (массивный и всеобъемлющий; больше для справки, хотя)
Комментарии:
1. Похоже, что ссылка на PDF не работает. Я нашел то, что кажется копией здесь , но я не могу быть уверен на 100%, поскольку я не знаю, как выглядел оригинал.
2. @John: Да, это тот самый. Отредактирует ссылку в, будь то эта или Oracle…
3. Есть ли что-нибудь еще, кроме T, E и? используется в дженериках? Если да, то что это такое и что они означают?
4. @sofs1: В
T
andE
нет ничего особенного — это просто идентификаторы. Вы могли бы написать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())
}