“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 参数使用不同的名称ET)。

第三个不是有效的声明 - ? 用作提供类型 参数 时使用的 通配符,例如List&lt;?&gt; 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:TE 没有什么特别之处——它们只是标识符。例如,您可以写 KeyValuePair&lt;K, V&gt;? 有特殊含义。 @JonSkeet “它们只是标识符”,这意味着它们必须遵循与类名、字段名、方法名等相同的名称约束。 Foo&lt;hello_world&gt; 有效。使用单个大写字母是一种命名标准,在Java Language Specification 中推荐:“类型变量名称应该简洁(如果可能的话,使用单个字符)但令人回味,并且不应包含小写字母。这使得很容易将类型参数与普通的类和接口区分开来。”【参考方案2】:

它比其他任何东西都更传统。

T 是一个类型 E 是一个元素(List&lt;E&gt;:元素列表) K 是 Key(在 Map&lt;K,V&gt; 中) V 是 Value(作为返回值或映射值)

它们是完全可以互换的(尽管在同一声明中存在冲突)。

【讨论】:

之间的字母只是一个名字。您在回答中描述的只是约定。它甚至不必是一个大写字母。你可以使用任何你喜欢的名字,就像你可以给类、变量等任何你喜欢的名字一样。 这篇文章有更详细和清晰的描述oracle.com/technetwork/articles/java/… 你没有对问号解释。投反对票。 为什么还是这个答案?【参考方案3】:

前面的答案解释了类型参数(T、E 等),但没有解释通配符“?”或它们之间的区别,所以我会解决这个问题。

首先要明确一点:通配符和类型参数是不一样的。类型参数定义了一种表示作用域类型的变量(例如 T),而通配符没有:通配符只定义了一组可用于泛型类型的允许类型。没有任何界限(extendssuper),通配符的意思是“在此处使用任何类型”。

通配符总是在尖括号之间,它只在泛型类型的上下文中有意义:

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&lt;? super Integer&gt;Integer 定义为通配符的下限,这意味着List 类型必须是Integer 或Integer 的超类型。泛型类型边界超出了我想要详细介绍的范围。简而言之,它允许您定义泛型类型可以是哪些类型。这使得多态处理泛型成为可能。例如。与:

public void foo(List<? extends Number> numbers) ...

您可以为numbers 传递List&lt;Integer&gt;List&lt;Float&gt;List&lt;Byte&gt; 等。没有类型限制,这将无法工作——泛型就是这样。

最后,这是一个方法定义,它使用通配符来做一些我认为你不能用其他方式做的事情:

public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) 
    numberSuper.add(elem);

numberSuper 可以是数字列表或数字的任何超类型(例如,List&lt;Object&gt;),elem 必须是数字或任何子类型。有了所有的边界,编译器就可以确定.add() 是类型安全的。

【讨论】:

"public void foo(List extends Number> numbers) ..." 应该 "extends" 是 "super" 吗? 没有。该示例的重点是显示多态支持数字列表和数字子类型的签名。为此,您使用“扩展”。即,“给我一个数字列表任何扩展数字的东西”(List、List 等等)。然后,这样的方法可能会遍历列表,并且对于每个元素“e”,执行例如 e.floatValue()。不管你传递的是 Number 的什么子类型(扩展名)——你总是能够“.floatValue()”,因为 .floatValue() 是 Number 的一种方法。 在您的最后一个示例中,“List super Number>”可能只是“List”,因为该方法不允许任何更通用的内容。 @jessarah 不。也许我的示例不清楚,但我在示例中提到 adder() 可以采用 List (Object 是 Number 的超类)。如果您希望它能够做到这一点,它必须具有签名“List”。这正是这里“超级”的意义所在。 这个答案很好地解释了通配符和类型参数之间的区别,这个答案应该有一个专门的问题。我最近对泛型进行了更深入的研究,这个答案帮助我把东西放在一起,简而言之,很多精确的信息,谢谢!【参考方案4】:

类型变量 可以是您指定的任何非原始类型:任何类类型、任何接口类型、任何数组类型,甚至是另一个类型变量。

最常用的类型参数名称有:

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中的泛型E、T、K、V吗?

java中的 class<T>和 class<?>类型 有啥区别,可以互相转换来用吗?是好举例来说明一下

Java泛型中T和问号(通配符)的区别

java中Collection方法里面的Object[] toArray() 和 <T> T[] toArray(T[] a)有啥区别吗?

Java泛型中的“超级”和“扩展”有啥区别[重复]

不明白java中的泛型和抽象类有啥区别,感觉他们作用一样啊,为啥要用2种方法呢