ChatGPT解决这个技术问题 Extra ChatGPT

“E”、“T”和“?”有什么区别?对于 Java 泛型?

我遇到这样的Java代码:

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}

以上三者之间有什么区别?他们在 Java 中将这种类型的类或接口声明称为什么?


J
Jon Skeet

好吧,前两者之间没有区别 - 它们只是为 type 参数 使用不同的名称(ET)。

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

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

Java 泛型教程

泛型语言指南

Java 编程语言中的泛型

Angelika Langer 的 Java 泛型常见问题解答(海量而全面;更多供参考)


看起来 PDF 的链接已损坏。我发现似乎是副本 here,但我不能 100% 确定,因为我不知道原件是什么样子。
@John:是的,就是这样。将编辑一个链接,无论是那个链接还是 Oracle 链接...
除了 T、E 和 之外还有什么?在泛型中使用?如果是这样,它们是什么,它们是什么意思?
@sofs1:TE 没有什么特别之处——它们只是标识符。例如,您可以写 KeyValuePair<K, V>? 虽然有特殊含义。
@JonSkeet “它们只是标识符”,这意味着它们必须遵循与类名、字段名、方法名等相同的名称约束。例如 Foo<hello_world> 是有效的。使用单个大写字母是一种命名标准,在 Java Language Specification 中推荐:“类型变量名称应该简洁(如果可能,使用单个字符)但令人回味,并且不应包含小写字母。这使得很容易将类型参数与普通类和接口区分开来。”
r
ratchet freak

它比其他任何东西都更约定俗成。

T 是一个类型

意味着是一个元素(List:元素列表)

K 是关键(在 Map 中)

V 是值(作为返回值或映射值)

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


< > 之间的字母只是一个名称。您在回答中描述的只是约定。它甚至不必是一个大写字母。你可以使用任何你喜欢的名字,就像你可以给类、变量等任何你喜欢的名字一样。
本文提供了更详细、更清晰的说明oracle.com/technetwork/articles/java/…
你没有对问号解释。否决。
无论如何,为什么这甚至是答案?
H
Hawkeye Parker

前面的答案解释了类型参数(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) {...}

您可以为 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 numbers) {...}" 应该 "extends" 是 "super" 吗?
不,该示例的重点是显示多态支持数字列表和数字子类型的签名。为此,您使用“扩展”。即,“给我一个数字列表或任何扩展数字的东西”(List、List 等等)。然后,这样的方法可能会遍历列表,并且对于每个元素“e”,执行例如 e.floatValue()。不管你传递的是 Number 的什么子类型(扩展名)——你总是可以使用“.floatValue()”,因为 .floatValue() 是 Number 的一种方法。
在您的最后一个示例中,“List”可能只是“List”,因为该方法不允许任何更通用的内容。
这个答案很好地解释了通配符和类型参数之间的区别,这个答案应该有一个专门的问题。我最近对泛型进行了更深入的研究,这个答案帮助我把东西放在一起,简而言之,很多精确的信息,谢谢!
这是唯一真正回答问题的答案
P
Peter Mortensen

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

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

E - 元素(被 Java 集合框架广泛使用)

K - 键

- 数字

- 类型

- 价值

在 Java 7 中,允许这样实例化:

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

W
Waqas Ahmed

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

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


T
Tiina

编译器在组成如下函数时会产生一个 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())
}