“ E”,“ T”和“”之间有什么区别?Java泛型?


261

我遇到了像这样的Java代码:

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}

以上三种之间的区别是什么?它们在Java中如何称呼这种类型的类或接口声明?


1
我怀疑有什么区别。我想这只是该类型参数的名称。最后一个甚至有效吗?
CodesInChaos 2011年

Answers:


231

好吧,前两个没有什么区别-它们只是为type参数ET)使用了不同的名称。

第三个不是有效的声明- ?用作提供类型实时使用的通配符,例如,意味着引用某种类型的列表,但我们不知道是什么。List<?> foo = ...foo

所有这些都是泛型,这是一个很大的话题。您可能希望通过以下资源了解它,尽管当然还有更多可用的方法:


1
似乎PDF链接已断开。我已经找到了这里的副本,但是由于我不知道原始的样子,所以不能100%确定。
约翰,

2
@John:是的。将编辑链接,无论是Oracle还是Oracle ...
Jon Skeet

除了T,E和之外,还有其他吗?用在泛型中?如果是这样,它们是什么意思?
sofs1

1
@ sofs1:有什么特别TE-他们只是标识符。你可以写KeyValuePair<K, V>例如。?虽然有特殊的意义。
乔恩·斯基特

215

它比其他任何事情都更加惯例。

  • T 是要成为一种类型
  • E 本来是一个元素(List<E>:元素列表)
  • K 是关键(在 Map<K,V>
  • V 是Value(作为返回值或映射值)

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


20
<>之间的字母只是一个名称。您在答案中描述的只是约定。它甚至不必是单个大写字母。您可以使用任何喜欢的名称,就像可以给类,变量等提供任何喜欢的名称一样。
杰斯珀,2015年


6
您没有向问号解释。不赞成投票。
Shinzou

129

前面的答案解释了类型参数(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<? 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列表或Number的任何超类型(例如List<Object>),并且elem必须是Number或任何子类型。有了所有的边界,编译器就可以确定the .add()是类型安全的。


“ public void foo(List <?extended Number>数字){...}”应该“ extends”是“ super”吗?
1a1a11a 2015年

1
否。该示例的重点是显示一个签名,该签名多态地支持Number列表和Number的子类型。为此,请使用“扩展”。即,“将数字列表任何扩展数字的列表传递给我”(List <Integer>,List <Float>,等等)。然后,可能会像这样的方法遍历列表,并对每个元素“ e”执行例如e.floatValue()。传递的Number的子类型(扩展名)无关紧要-您始终可以使用“ .floatValue()”,因为.floatValue()是Number的方法。
Hawkeye Parker

在您的最后一个示例中,“ List <?super Number>”可以简单地是“ List <Number>”,因为该方法不允许任何更通用的方法。
jessarah '16

@jessarah不。也许我的示例不清楚,但是我在示例中提到adder()可以采用List <Object>(Object是Number的超类)。如果希望它能够执行此操作,则它必须具有签名“ List <?super Number>”。这正是“超级”的要点。
Hawkeye Parker

2
该答案在解释通配符和类型参数之间的区别方面非常好,此答案应该有一个专门的问题。最近,我对泛型有了更深入的了解,这个答案对我的工作很有帮助,简而言之,很多准确的信息,谢谢!
Testo Testini

27

类型变量<T>可以是您指定的任何非基本类型:任何类类型,任何接口类型,任何数组类型,甚至另一个类型变量。

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

  • E-元素(由Java Collections Framework广泛使用)
  • K键
  • N-号码
  • T型
  • V-值

在Java 7中,可以这样实例化:

Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6

3

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

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中使用的这些名称


2

当编译器组成如下函数时,它将捕获每个通配符(例如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())
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.