【问题标题】:Java generics : wildcardsJava 泛型:通配符
【发布时间】:2013-02-28 01:03:59
【问题描述】:

所以我正在阅读泛型以重新熟悉这些概念,尤其是在涉及通配符的地方,因为我几乎从不使用它们或遇到它们。从我所做的阅读中,我无法理解他们为什么使用通配符。我经常遇到的一个例子如下。

void printCollection( Collection<?> c ) {
  for (Object o : c){
    System.out.println(o);
  }
}

你为什么不把它写成:

<T> void printCollection( Collection<T> c ) {
    for(T o : c) {
        System.out.println(o);
    }
}

oracle 网站的另一个例子:

public static double sumOfList(List<? extends Number> list) {
    double s = 0.0;
    for (Number n : list)
        s += n.doubleValue();
    return s;
}

为什么不写成

public static <T extends Number> double sumOfList(List<T> list) {
    double s = 0.0;
    for (Number n : list)
        s += n.doubleValue();
    return s;
}

我错过了什么吗?

【问题讨论】:

标签: java generics


【解决方案1】:

来自Oracle

出现的一个问题是:什么时候应该使用泛型方法,什么时候应该使用通配符类型?为了理解答案,让我们检查一下 Collection 库中的一些方法。

interface Collection<E> {
     public boolean containsAll(Collection<?> c);
     public boolean addAll(Collection<? extends E> c);
 }

我们可以在这里使用泛型方法:

interface Collection<E> {
     public <T> boolean containsAll(Collection<T> c);
     public <T extends E> boolean addAll(Collection<T> c);
     // Hey, type variables can have bounds too!
 }

但是,在 containsAll 和 addAll 中,类型参数 T 只使用一次。返回类型不依赖于类型参数,也不依赖于方法的任何其他参数(在这种情况下,只有一个参数)。这告诉我们类型参数被用于多态性;它的唯一作用是允许在不同的调用站点使用各种实际的参数类型。如果是这种情况,应该使用通配符。通配符旨在支持灵活的子类型化,这正是我们在此试图表达的。

所以对于第一个示例,这是因为操作不依赖于类型。

第二个,因为它只依赖于 Number 类。

【讨论】:

  • 谢谢,所有的解释都很好,但是由于某种原因,这个解释最能帮助我理解。
【解决方案2】:

为什么要让事情变得比需要的更复杂?这些示例演示了the simplest thing that could possibly work——这些示例并不是为了说明通用方法。

你为什么不把它写成:

<T> void printCollection( Collection<T> c ) {
    for(T o : c) {
        System.out.println(o);
    }
}

因为System.out.println() 可以接受对象,所以不需要更具体的。

为什么不写成这样

public static <T extends Number> double sumOfList(List<T> list) {
    double s = 0.0;
    for (Number n : list)
        s += n.doubleValue();
    return s;
}

再次,因为您不需要为每个不同的T extends Number 进行不同的参数化。接受List&lt;? extends Number&gt; 的非泛型方法就足够了。

【讨论】:

  • 我想我可能只是误解了两者之间的区别,还是它们相同?我会遇到什么情况无法使用 T 而不是解决?我有一种感觉,我完全错过了通配符的意义。
  • @JonTaylor:没有什么情况是用T 代替? 解决不了的,但重点是? 不需要名称并且表示你真的不在乎类型是什么。
  • @LouisWasserman Class.forName 返回 Class&lt;?&gt; 可能是只有通配符适用的示例。
【解决方案3】:

确实,如果方法参数类型具有带上限的第一级通配符,则可以将其替换为类型参数。

反例(通配符不能用类型参数代替)

    List<?> foo()  // wildcard in return type  

    void foo(List<List<?>> arg)   // deeper level of wildcard

    void foo(List<? super Number> arg)   // wildcard with lower bound

现在对于可以通过通配符或类型参数解决的情况

        void foo1(List<?> arg)

    <T> void foo2(List<T> arg)

一般认为foo1()foo2()更时尚。这可能有点主观。我个人认为foo1()签名比较容易理解。而foo1() 被业界广泛采用,因此最好遵循惯例。

foo1() 也将arg 处理得更抽象一点,因为你不能轻易地在foo1() 中做arg.add(something)。当然,这很容易解决(即将 arg 传递给foo2()!)。公共方法看起来像 foo1() 也是一种常见的做法,它在内部转发到私有 foo2()

在某些情况下通配符不起作用并且需要类型参数:

    <T> void foo(List<T> foo1, List<T> foo2);  // can't use 2 wildcards.

到目前为止,这个讨论是关于方法签名的。在其他地方,在无法引入类型参数的地方,通配符可能是必不可少的。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2011-11-12
  • 2017-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多