我遇到像这样的Java代码:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
上述三个方面有什么区别,他们在Java中称这种类或接口声明是什么?
答案 0 :(得分:208)
前两个之间没有区别 - 他们只是为类型参数(E
或T
)使用不同的名称。
第三个不是有效的声明 - ?
用作通配符,它在提供类型参数时使用,例如List<?> foo = ...
表示foo
引用某种类型的列表,但我们不知道是什么。
所有这些都是 generics ,这是一个非常大的话题。您可能希望通过以下资源了解它,尽管当然有更多可用的资源:
答案 1 :(得分:196)
它比其他任何事情都更为常规。
T
是一个Type E
是一个元素(List<E>
:元素列表)K
是密钥(位于Map<K,V>
)V
是值(作为返回值或映射值)它们完全可以互换(尽管存在相同声明中的冲突)。
答案 2 :(得分:98)
前面的答案解释了类型参数(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> {...}
通配符可以有下限;类型参数不能:
public void bar(List<? super Integer> list) {...}
在上面,List<? super Integer>
将Integer
定义为通配符的下限,这意味着List类型必须是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 of Number或任何超类型的数字(例如List<Object>
),elem
必须是Number或任何子类型。通过所有边界,编译器可以确定.add()
是类型安全的。
答案 3 :(得分:26)
类型变量&lt; T&gt;可以是您指定的任何非基本类型:任何类类型,任何接口类型,任何数组类型,甚至是其他类型变量。
最常用的类型参数名称是:
在Java 7中,允许实例化如下:
Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6
答案 4 :(得分:1)
编译器在组成如下函数时会生成capture for each wildcard(例如,列表中的问号):
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())
}
答案 5 :(得分:0)
最常用的类型参数名称为:
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
您将看到这些名称在整个Java SE API中使用