【问题标题】:In Java, what can a wild card do that regular generics cannot do?在 Java 中,通配符能做什么常规泛型不能做什么?
【发布时间】:2015-04-24 20:27:56
【问题描述】:

我是 Java 新手。在this 文档中,他们将此作为使用通配符的用例:

static void printCollection(Collection c) {
    Iterator i = c.iterator();
    for (int k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}

这是他们的解决方案:

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

但我可以在没有通配符的情况下做同样的事情:

static <T> void printCollection(Collection<T> c) {
    Iterator i = c.iterator();
    for (int k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}

谁能告诉我一个简单的用例,其中常规泛型不起作用但通配符可以?

更新:这里的答案When to use wildcards in Java Generics? 并没有告诉我们需要通配符。实际上是相反的。

【问题讨论】:

  • 我不完全确定,但我相信没有通配符的最后一个版本可能会在调用具有不同集合类型的方法时产生特定于每个 Collection 类型的同一方法的多个版本。有通配符的人会处理所有这些。
  • @NobuGames 在 Java 中,这将是一种方法,因为泛型在编译时被删除。
  • the official docs 很好地回答了这个问题
  • 通配符可以(糟糕地)模拟协变和逆变。
  • Java 能做什么那个程序集不能做什么?

标签: java generics bounded-wildcard unbounded-wildcard


【解决方案1】:

通配符允许我们做的一件事是声明对特定类型参数不可知的类型,例如“任何类型列表的列表”

List<List<?>> listOfAnyList = ...;

listOfAnyList.add( new ArrayList<String>() );
listOfAnyList.add( new ArrayList<Double>() );

如果没有通配符,这是不可能的:*,因为元素列表可能有不同的类型。

如果我们尝试capture它,我们会发现我们做不到:

static <E> void m(List<List<E>> listOfParticularList) {}

m( listOfAnyList ); // <- this won't compile

通配符允许我们做的另一件事是类型参数不能设置下限。 (可以使用extends 绑定声明类型参数,但不能使用super 绑定声明。**)

class Protector {
    private String secretMessage = "abc";

    void pass(Consumer<? super String> consumer) {
        consumer.accept( secretMessage );
    }
}

假设pass 被声明为采用Consumer&lt;String&gt;。现在假设我们有一个Consumer&lt;Object&gt;

class CollectorOfAnything implements Consumer<Object> {
    private List<Object> myCollection = new ArrayList<>();

    @Override
    public void accept(Object anything) {
        myCollection.add( anything );
    }
}

问题是:我们不能将它传递给接受Consumer&lt;String&gt; 的方法。声明Consumer&lt;? super String&gt; 意味着我们可以传递任何接受String 的消费者。 (另见Java Generics: What is PECS?。)

大多数时候,通配符只是让我们做出整洁的声明。

如果我们不需要使用类型,我们就不必为它声明类型参数。


* 技术上也可以使用raw type,但不鼓励使用原始类型。

** 我不知道为什么 Java 不允许 super 作为类型参数。 4.5.1. Type Arguments of Parameterized Types 可能暗示它与类型推断的限制有关:

与在方法签名中声明的普通类型变量不同,使用通配符时不需要类型推断。因此,允许在通配符 […] 上声明下限。

【讨论】:

  • 现在如果你也能包含一个有用的通配符的实际应用......因为我从来没有找到它们
  • @Mehrdad Map&lt;Class&lt;?&gt;, Something&gt; ? super 怎么没用?到处都在使用它。
  • 在我看来,Class&lt;?&gt; 是必需的,因为不鼓励使用原始类型,而不是因为通配符增加了任何值。至于? super...我从来没有觉得有必要使用它,但也许只有我一个人;你在哪里看到有必要?
  • @Mehrdad 原始类型和通配符只是同一枚硬币的两个方面。我假设我们可以同意其中一个必要的偶尔? super 对于编写通用代码很有用,例如Iterable#forEach(Consumer&lt;? super T&gt;).
  • 是的,我们愿意。关于? super 的例子,我想这个例子在旧版本的Java 中是有意义的,但是创建一个接受T 然后调用正确方法的方法也同样容易。但是,对于较新的版本,您只需执行 Iterable#forEach(Consumer&lt;T&gt;) 然后调用它作为 forEach(x -&gt; f(x))f 正确插入,即使它接受 T 的超类,对吧?
【解决方案2】:

T 代表该数据结构的通用类型。在您的最后一个示例中,您没有使用它,并且它不是实际类型(例如 String),并且因为您不使用它,所以在这种情况下并不重要。

例如,如果你有一个 Collection 并试图将它传递给一个接受 Collection 的方法,那么这是可行的,因为类路径上没有类型 T,因此它被视为一个变量。如果您尝试将相同的 Collection 传递给接受 Collection 的方法,那将不起作用,因为您的类路径中有 String,因此它不是变量。

【讨论】:

    【解决方案3】:

    列表为例。

    • List&lt;?&gt; 可以是List&lt;A&gt; 的父类。

    例如,

    List<B> bList = new ArrayList<>(); // B is a class defined in advance
    List<?> list = bList;
    

    在这种情况下你永远不能使用&lt;T&gt;

    • &lt;?&gt; 具有通配符捕获。

    这里,

      void foo(List<?> i) {
            i.set(0, i.get(0));
        }
    

    上面的代码无法编译。你可以修复它:

        void foo(List<?> i) {
            fooHelper(i);
        }
    
        // wildcard can be captured through type inference.
        private <T> void fooHelper(List<T> l) {
            l.set(0, l.get(0));
        }
    

    查看更多,http://docs.oracle.com/javase/tutorial/java/generics/capture.html

    目前只能想到这两个,以后可能会更新。

    【讨论】:

      猜你喜欢
      • 2016-11-20
      • 2011-08-23
      • 2018-05-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多