通配符与类型参数

Wildcard vs 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 到底有什么区别,什么时候用什么?

为什么使用 class 定义,? 不起作用,但它适用于 List,为什么 T 适用于 class 定义,但不适用于List?

在哪里声明泛型

您不能在引入泛型类型标记之前使用它T

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

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

在这里您可以在 class 广泛的层面上介绍您的通用类型标记:

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 无效,因为它没有意义,并且不提供比 TNumberObject 更多的信息,具体取决于什么你正在努力实现。您可以在 Java generic methods: super can't be used?

阅读更多相关信息

通配符

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

例如像

这样的方法
public static void foo(List<? super Dog> list) { ... }

可以用 List<Dog>List<Animal> 甚至 List<Object> 调用。我们称这样的列表为 consumer of Dogs。准确地说,这些是 所有可以接受狗的列表,所以 list.add(new Dog()) 可以。

另一边,我们有

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

可以用 List<Dog>List<Chihuahua> 调用。我们称这样的列表为 Dog 生产者 (或提供者)。准确地说,这些是所有可以提供狗的列表。所以 Dog dog = list.get(0) 会起作用。

您可以在 What is PECS (Producer Extends Consumer Super)?

阅读更多关于什么是通配符及其工作原理的详细信息

什么时候使用哪个?

一般来说,当您实际上仍然需要在整个代码中维护类型安全时,您会使用泛型类型标记 T。 IE。当您需要能够 给类型命名时 。否则你使用通配符 ?.

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

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);
    }
}