【发布时间】:2023-03-17 18:34:01
【问题描述】:
我遇到过这样的 Java 代码:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
以上三者之间有什么区别,他们在 Java 中称这种类型的类或接口声明是什么?
【问题讨论】:
我遇到过这样的 Java 代码:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
以上三者之间有什么区别,他们在 Java 中称这种类型的类或接口声明是什么?
【问题讨论】:
前两者之间没有区别 - 他们只是为 type 参数使用不同的名称(E 或 T)。
第三个不是有效的声明 - ? 用作提供类型 参数 时使用的 通配符,例如List<?> foo = ... 表示foo 指的是某种类型的列表,但我们不知道是什么。
所有这些都是泛型,这是一个相当大的话题。您可能希望通过以下资源了解它,当然还有更多可用资源:
【讨论】:
T 和 E 没有什么特别之处——它们只是标识符。例如,您可以写 KeyValuePair<K, V>。 ? 有特殊含义。
Foo<hello_world> 有效。使用单个大写字母是一种命名标准,在Java Language Specification 中推荐:“类型变量名称应该简洁(如果可能的话,使用单个字符)但令人回味,并且不应包含小写字母。这使得很容易将类型参数与普通的类和接口区分开来。”
它比其他任何东西都更传统。
T 是一个类型 E 是一个元素(List<E>:元素列表)K 是 Key(在 Map<K,V> 中)V 是 Value(作为返回值或映射值)它们是完全可以互换的(尽管在同一声明中存在冲突)。
【讨论】:
前面的答案解释了类型参数(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() 是类型安全的。
【讨论】:
类型变量
最常用的类型参数名称有:
在 Java 7 中允许这样实例化:
Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6
【讨论】:
最常用的类型参数名称有:
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 中使用
【讨论】:
编译器在组成如下函数时会生成一个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())
}
【讨论】: