好吧,前两者之间没有区别 - 它们只是为 type 参数 使用不同的名称(E
或 T
)。
第三个不是有效的声明 - ?
用作提供类型 argument 时使用的 通配符,例如 List<?> foo = ...
表示 foo
引用到某种类型的列表,但我们不知道是什么。
所有这些都是泛型,这是一个相当大的话题。您可能希望通过以下资源了解它,当然还有更多可用资源:
Java 泛型教程
泛型语言指南
Java 编程语言中的泛型
Angelika Langer 的 Java 泛型常见问题解答(海量而全面;更多供参考)
它比其他任何东西都更约定俗成。
T 是一个类型
意味着是一个元素(List
K 是关键(在 Map
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),您可以在范围内的其他地方使用它;通配符没有。否则,类型参数和通配符之间有两个很大的区别:
类型参数可以有多个边界类;通配符不能:
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) {...}
您可以为 numbers
传递 List<Integer>
、List<Float>
、List<Byte>
等。没有类型限制,这将不起作用——泛型就是这样。
最后,这是一个方法定义,它使用通配符执行我认为您无法以其他方式执行的操作:
public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
numberSuper.add(elem);
}
numberSuper
可以是数字列表或数字的任何超类型(例如,List<Object>
),elem
必须是数字或任何子类型。通过所有边界,编译器可以确定 .add()
是类型安全的。
类型变量
最常用的类型参数名称是:
E - 元素(被 Java 集合框架广泛使用)
K - 键
- 数字
- 类型
- 价值
在 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
您将看到在整个 Java SE API 中使用的这些名称
编译器在组成如下函数时会产生一个 capture for each wildcard(例如,List 中的问号):
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())
}
不定期副业成功案例分享
T
和E
没有什么特别之处——它们只是标识符。例如,您可以写KeyValuePair<K, V>
。?
虽然有特殊含义。Foo<hello_world>
是有效的。使用单个大写字母是一种命名标准,在 Java Language Specification 中推荐:“类型变量名称应该简洁(如果可能,使用单个字符)但令人回味,并且不应包含小写字母。这使得很容易将类型参数与普通类和接口区分开来。”