区别?(通配符)和Java中的Type参数

Tar*_*rik 27 java generics

有人可以解释一下这两种方法之间的区别吗?它们一样吗?就他们解决的问题而言,他们看起来与我相同.如果它们相同,为什么需要?

方法#1,无界限

public static void printList(List<?> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}
Run Code Online (Sandbox Code Playgroud)

方法#2,无界:

public static <T> void printList(List<T> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}
Run Code Online (Sandbox Code Playgroud)

方法#1,有界

public static void printList(List<? extends Number> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}
Run Code Online (Sandbox Code Playgroud)

方法#2,有界:

public static <T extends Number> void printList(List<T> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}
Run Code Online (Sandbox Code Playgroud)

Jas*_*n C 30

它们是相同的,因为它们接受相同的参数类型.

但是,使用T(或其他)标识类型可以让您在其他地方引用类型.

编辑:示例:

您的无界示例未充分利用参数化类型的功能.你有:

public static <T> void printList(List<T> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}
Run Code Online (Sandbox Code Playgroud)

这对于打印字符串表示的示例来说已经足够了,但请考虑这一点(非常人为,并且没有错误处理):

public static <T> T getSecondItem (List<T> list) {
    T item = list.get(1);
    return item;
}
Run Code Online (Sandbox Code Playgroud)

返回类型是T,它允许您安全地执行这样的操作,使用编译时类型检查:

class MyClass {
    public void myMethod () { }
}

void somewhere () {
    List<MyClass> items = ...;
    getSecondItem(items).myMethod();
}
Run Code Online (Sandbox Code Playgroud)

命名类型还允许您在多个位置共享相同的类型约束,例如:

public <T> int compareLists (List<T> a, List<T> b) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

如果你没有名字的类型,你不能指定约束ab是相同的列表类型(你可以使用List<? extends T>更多的灵活性).

你还问过"我为什么需要??".真正的答案是:你没有.我想,这主要是为了美学.Java致力于成为一种精确且无杂乱的语言.在许多情况下,您根本不关心您所指的类型.在这些情况下,您可以使用?不使用未使用的类型参数声明的代码.

  • @Katona为了回应Jason所说的,通配符捕获本质上是类型参数,永远不会再次引用.但它们并不总是捷径.请注意,它们可能在方法参数之外的其他地方使用 通配符可以在任何地方使用,并不局限于类型参数之类的范围 - 例如`static final List <?> MY_LIST;`不能使用类型参数.还有*嵌套通配符*,它们具有[完全不同的语义](http://stackoverflow.com/questions/3546745).方法`void m(List <List <?>> l)`与`<T> void m(List <List <T >> l)`不同. (5认同)
  • @Katona不是真的.但是它允许你保持你的代码没有未使用的类型参数,例如`void op(List <?extends Base> a,List <?extends Base> b)`而不是`void <T extends Base,U extends Base> op (List <T> a,List <U> b)`. (3认同)
  • 那为什么我需要使用`?`? (2认同)
  • 通过使用通配符,你明确地说:我不关心列表元素的具体类型,我所做的并不依赖于这种类型.如果方法必须返回T,或者将T传递给其他泛型类型,那么您将使用类型化方法. (2认同)

sco*_*ttb 5

在您的示例中,完全没有任何区别.每个都会产生相同的输出.

泛型的最佳使用和解释要求您了解类型参数的语义以及有关参数角色的内容.原因在于,在上面的第一个例子(无界外卡)中,语义是"未知类型的对象列表"和"将生成(不消耗)List<?>实例的参数" .

上面的方法只是生成每个List<?>对象并调用每个对象toString().所有对象都保证有一个toString()方法,因此没有必要为此目的了解对象的类型.这正是无界通配符是此方法参数的最佳选择的原因:要生成List<?>实例并调用toString()它们,不必了解对象的类型.

请注意,如果它?具有相同的语义("未知类型的对象列表")但具有不同的目的("List将使用未知类型的对象"),事物将会非常非常彻底地改变,以至于它可能是不可取的(或者非常困难)使用通配符参数(至少没有辅助方法来捕获对象的类型).

通常不可能为所有情况断言一个通用参数形式.使用的最佳形式(通配符与具体;有界与无界;扩展与超级)取决于类型参数的语义以及方法中参数的作用.


小智 5

如上所述,?泛型代码不需要对类型的任何引用。正如 Java Trails 中所指出的,这可能是一个类,其中的属性独立于任何类型,例如集合的长度。

另一个原因?是它为通用代码只需要类 Object 提供的行为的情况提供了减少歧义的语法。正如上面所指出的,参数类型<T>可以工作,但 T 被定义然后从未使用的事实可能表明开发人员遗漏了一些东西。

所以如果<T>因为语义歧义而被丢弃并且<?>不可用,那么开发人员会很想写<Object>. 然而,这不允许将通用代码用于任何类型,只能用于 Object 类型。

所以,<?>更准确。