“E”、“T”和“”有啥区别?对于 Java 泛型?
Posted
技术标签:
【中文标题】“E”、“T”和“”有啥区别?对于 Java 泛型?【英文标题】:What is the difference between 'E', 'T', and '?' for Java generics?“E”、“T”和“”有什么区别?对于 Java 泛型? 【发布时间】:2011-08-25 21:07:17 【问题描述】:我遇到过这样的 Java 代码:
public interface Foo<E>
public interface Bar<T>
public interface Zar<?>
以上三者之间有什么区别,他们在 Java 中称这种类型的类或接口声明是什么?
【问题讨论】:
【参考方案1】:前两者之间没有区别 - 他们只是为 type 参数使用不同的名称(E
或 T
)。
第三个不是有效的声明 - ?
用作提供类型 参数 时使用的 通配符,例如List<?> foo = ...
表示foo
指的是某种类型的列表,但我们不知道是什么。
所有这些都是泛型,这是一个相当大的话题。您可能希望通过以下资源了解它,当然还有更多可用资源:
Java Tutorial on Generics Language guide to generics Generics in the Java programming language Angelika Langer 的 Java Generics FAQ(内容丰富且全面;但更多供参考)【讨论】:
看起来 PDF 的链接已损坏。我发现似乎是一个副本 here,但我不能 100% 确定,因为我不知道原件是什么样子。 @John:是的,就是这样。将编辑一个链接,无论是那个链接还是 Oracle 链接... 除了 T、E 和 之外还有什么?在泛型中使用?如果是这样,它们是什么,它们是什么意思? @sofs1:T
和 E
没有什么特别之处——它们只是标识符。例如,您可以写 KeyValuePair<K, V>
。 ?
有特殊含义。
@JonSkeet “它们只是标识符”,这意味着它们必须遵循与类名、字段名、方法名等相同的名称约束。 Foo<hello_world>
有效。使用单个大写字母是一种命名标准,在Java Language Specification 中推荐:“类型变量名称应该简洁(如果可能的话,使用单个字符)但令人回味,并且不应包含小写字母。这使得很容易将类型参数与普通的类和接口区分开来。”【参考方案2】:
它比其他任何东西都更传统。
T
是一个类型
E
是一个元素(List<E>
:元素列表)
K
是 Key(在 Map<K,V>
中)
V
是 Value(作为返回值或映射值)
它们是完全可以互换的(尽管在同一声明中存在冲突)。
【讨论】:
之间的字母只是一个名字。您在回答中描述的只是约定。它甚至不必是一个大写字母。你可以使用任何你喜欢的名字,就像你可以给类、变量等任何你喜欢的名字一样。 这篇文章有更详细和清晰的描述oracle.com/technetwork/articles/java/… 你没有对问号解释。投反对票。 为什么还是这个答案?【参考方案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> & 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()
是类型安全的。
【讨论】:
"public void foo(List extends Number> numbers) ..." 应该 "extends" 是 "super" 吗? 没有。该示例的重点是显示多态支持数字列表和数字子类型的签名。为此,您使用“扩展”。即,“给我一个数字列表或任何扩展数字的东西”(List类型变量
最常用的类型参数名称有:
E - 元素(Java 集合框架广泛使用) 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
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types
您将看到这些名称在整个 Java SE API 中使用
【讨论】:
【参考方案6】:编译器在组成如下函数时会生成一个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())
【讨论】:
以上是关于“E”、“T”和“”有啥区别?对于 Java 泛型?的主要内容,如果未能解决你的问题,请参考以下文章
java中的 class<T>和 class<?>类型 有啥区别,可以互相转换来用吗?是好举例来说明一下
java中Collection方法里面的Object[] toArray() 和 <T> T[] toArray(T[] a)有啥区别吗?