通配符与TypeParameter


class Employee<T extends Number> {  // valid
}
class Employee<? extends Number> { // invalid
}
private static void test(List<? super Number> list1) { // valid
}
private static <T>void test(List<T super Number> list1) { // invalid
}

?T的区别是什么?什么时候使用?

为什么对于类定义,?不起作用,但它与List起作用,为什么T与类定义起作用,而与List不起作用?

在哪里声明泛型

在引入通用类型令牌T之前,不能使用它。

在方法示例中,您试图在错误的位置声明T,这是无效语法。你必须事先介绍一下。

然而,对于类示例,您已经将其放在了正确的位置。

在这里,您可以在类级别上介绍您的通用类型令牌:

public class Foo< HERE > { ... }

这就是你只针对一种方法的方法:

public < HERE > void foo(...) { ... }

有界泛型

在这两种情况下,您都可以绑定T,如T extends Number,然后相应地使用它:

public class Foo<T extends Number> { ... }
// or
public <T extends Number> void foo(...) { ... }

在您介绍了T之后,您将像那样使用它。以List<T>为例。

public <T extends Number> void foo(List<T> list) { ... }

请注意,T super Number在上无效,因为它没有什么意义,并且不会提供比TNumber或简单的Object更多的信息,这取决于您试图实现的目标。您可以在Java泛型方法中阅读更多关于这方面的内容:super不能使用吗?


通配符

通配符是另一回事。它们不是必须首先引入的泛型类型标记,例如T。相反,它们明确了您想要接受的类型范围。

例如,像这样的方法

public static void foo(List<? super Dog> list) { ... }

可以用List<Dog>List<Animal>或者甚至List<Object>来调用。我们将这样的列表称为Dogs的消费者。准确地说,这些列表是所有接受狗的列表,因此list.add(new Dog())将起作用。

另一方面,我们有

public static void foo(List<? extends Dog> list) { ... }

其可以用CCD_ 23或CCD_。我们将这样的列表称为Dogs的生产者(或提供者(。准确地说,这些列表是所有可以提供狗的列表。因此Dog dog = list.get(0)将起作用。

您可以在what is PECS(Producer Extends Consumer Super(上阅读更多关于通配符的详细信息以及它们是如何工作的?


何时使用哪个

通常,当您仍然需要在整个代码中维护类型安全性时,您将使用泛型类型令牌T。也就是说,当你需要时,给类型一个名称。否则使用通配符?

例如,假设您想要创建一个方法,该方法接受一个列表和一个要添加到其中的元素:

public static <T> void addToList(List<T> list, T element) {
list.add(element);
}

您需要引入T,以确保列表的类型与给定的元素匹配。否则,有人可能会做addToList(dogs, cat),而这是你不想要的。

如果您不需要实际命名类型,也可以只使用通配符。例如,一种获取列表并打印其所有内容的方法:

public static void printAll(List<?> list) {
for (Object object : list) {
System.out.println(object);
}
}

最新更新