Java泛型的'?','E'和'T'有什么区别?

ace*_*ace 241 java generics

我遇到像这样的Java代码:

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}
Run Code Online (Sandbox Code Playgroud)

上述所有这三者之间有什么区别,他们在Java中称这种类或接口声明是什么?

Jon*_*eet 219

那么前两者之间没有区别 - 他们只是为类型参数(ET)使用不同的名称.

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

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

  • PDF 链接似乎已损坏。我发现了似乎是副本的内容[此处](https://www.cs.rice.edu/~cork/312/Readings/GenericsTutorial.pdf),但我不能 100% 确定,因为我不知道不知道原作是什么样子的。 (2认同)
  • @John:是的,就是那个.将编辑一个链接,无论是那个还是Oracle一个... (2认同)
  • @sofs1:`T` 和 `E` 没有什么特别之处——它们只是标识符。例如,您可以编写“KeyValuePair&lt;K, V&gt;”。`?` 有特殊的含义。 (2认同)
  • @JonSkeet *“它们只是标识符”*,这意味着它们必须遵循与类名、字段名、方法名等相同的名称约束。例如 `Foo&lt;hello_world&gt;` 是有效的。使用单个大写字母是一种命名标准,[Java 语言规范](https://docs.oracle.com/javase/specs/jls/se14/html/jls-6.html#jls -6.1): *“类型变量名称应该简洁(如果可能的话是单个字符)但令人印象深刻,并且不应包含小写字母。这样可以轻松区分类型参数与普通类和接口。”* (2认同)

rat*_*eak 201

它比其他任何东西都更常见.

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

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

  • <>之间的字母只是一个名字.你在答案中描述的只是惯例.它甚至不必是一个单一的大写字母; 您可以使用任何您喜欢的名称,就像您可以给出类,变量等任何您喜欢的名称. (18认同)
  • 您没有向问号解释。不赞成投票。 (3认同)

Haw*_*ker 111

前面的答案解释了类型参数(T,E等),但不解释通配符,"?"或它们之间的差异,所以我将解决这个问题.

首先,要明确:通配符和类型参数不一样.其中类型参数定义了一种表示范围类型的变量(例如,T),通配符不是:通配符只定义了一组可用于泛型类型的允许类型.没有任何边界(extendssuper),通配符意味着"在这里使用任何类型".

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

public void foo(List<?> listOfAnyType) {...}  // pass a List of any type
Run Code Online (Sandbox Code Playgroud)

决不

public <?> ? bar(? someType) {...}  // error. Must use type params here
Run Code Online (Sandbox Code Playgroud)

要么

public class MyGeneric ? {      // error
    public ? getFoo() { ... }   // error
    ...
}
Run Code Online (Sandbox Code Playgroud)

它们重叠的地方会变得更加混乱.例如:

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.
Run Code Online (Sandbox Code Playgroud)

方法定义可能存在很多重叠.以下是功能相同的:

public <T> void foo(List<T> listOfT) {...}
public void bar(List<?> listOfSomething)  {...}
Run Code Online (Sandbox Code Playgroud)

那么,如果有重叠,为什么要使用其中一个呢?有时,它实际上只是风格:有人说如果你不需要类型参数,你应该使用通配符来使代码更简单/更易读.我在上面解释了一个主要区别:type params定义了一个类型变量(例如,T),你可以在范围的其他地方使用它; 通配符没有.否则,类型参数和通配符之间有两个很大的区别:

类型params可以有多个边界类; 通配符不能:

public class Foo <T extends Comparable<T> & Cloneable> {...}
Run Code Online (Sandbox Code Playgroud)

通配符可以有下限; 类型参数不能:

public void bar(List<? super Integer> list) {...}
Run Code Online (Sandbox Code Playgroud)

在上面,List<? super Integer>定义Integer为通配符的下限,这意味着List类型必须是Integer或Integer的超类型.泛型类型边界超出了我想要详细介绍的范围.简而言之,它允许你定义哪些类型的泛型类型就可以了.这使得可以多态地处理泛型.例如:

public void foo(List<? extends Number> numbers) {...}
Run Code Online (Sandbox Code Playgroud)

你可以通过一个List<Integer>,List<Float>,List<Byte>,等了numbers.没有类型边界,这将无法工作 - 这就是泛型.

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

public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
    numberSuper.add(elem);
}
Run Code Online (Sandbox Code Playgroud)

numberSuper可以是数字列表或任何超类型的数字(例如List<Object>),并且elem必须是数字或任何子类型.通过所有边界,编译器可以确定.add()类型安全.

  • 这个答案非常适合解释通配符和类型参数之间的差异,这个答案应该有一个专门的问题.我最近在仿制药方面做得更深入,这个答案帮助了我很多东西,简而言之,很多精确的信息,谢谢! (2认同)
  • 这是真正回答问题的唯一答案 (2认同)

小智 28

类型变量<T>可以是您指定的任何非基本类型:任何类类型,任何接口类型,任何数组类型,甚至是其他类型变量.

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

  • E - Element(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
Run Code Online (Sandbox Code Playgroud)


Waq*_*med 8

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

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
Run Code Online (Sandbox Code Playgroud)

您将看到在整个 Java SE API 中使用的这些名称