Я сталкиваюсь с кодом Java, как это:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
В чем разница между всеми тремя из вышеперечисленных и как они называют этот тип объявлений класса или интерфейса в Java?
Я сталкиваюсь с кодом Java, как это:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
В чем разница между всеми тремя из вышеперечисленных и как они называют этот тип объявлений класса или интерфейса в Java?
Ответы:
Ну, нет никакой разницы между первыми двумя - они просто используют разные имена для параметра типа ( E
или T
).
Третье не является допустимым объявлением - ?
используется в качестве подстановочного знака, который используется при предоставлении аргумента типа , например, List<?> foo = ...
означает, что foo
относится к списку некоторого типа, но мы не знаем, что.
Все это дженерики , и это довольно большая тема. Вы можете узнать об этом через следующие ресурсы, хотя, конечно, есть и другие:
T
и E
они просто идентификаторы. Вы могли бы написать, KeyValuePair<K, V>
например. ?
имеет особое значение, хотя.
Это больше обычного, чем все остальное.
T
предназначен для типа E
должен быть элементом ( List<E>
список элементов) K
Ключ (в Map<K,V>
) V
Значение (в качестве возвращаемого значения или сопоставленного значения) Они полностью взаимозаменяемы (несмотря на конфликты в одной и той же декларации).
Предыдущие ответы объясняют параметры типа (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()
он безопасен для типов.
Переменная типа <T> может быть любым указанным вами не примитивным типом: любым типом класса, любым типом интерфейса, любым типом массива или даже переменной другого типа.
Наиболее часто используемые имена параметров типа:
В Java 7 разрешено создавать такие экземпляры:
Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 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
Компилятор сделает захват для каждого подстановочного знака (например, вопросительного знака в Списке), когда он составляет такую функцию:
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())
}