【问题标题】:Difference between an unbound wildcard and a raw type未绑定通配符和原始类型之间的区别
【发布时间】:2013-01-09 16:48:46
【问题描述】:

我正在阅读有关泛型的文章,但我不明白对未绑定通配符的需求以及它与原始类型有何不同。我读了this question,但还是没搞清楚。在Java tutorial page for unbound wildcard我得到了以下两点,我没有理解第一点:

  • 如果您正在编写可以使用Object 类中提供的功能实现的方法。
  • 当代码使用泛型类中不依赖于类型参数的方法时。例如,List.size()List.clear()。其实Class<?>之所以这么常用,是因为Class<T>中的大部分方法都不依赖T

谁能解释一下外行语言中未绑定通配符和原始类型之间的区别。

List<?>List<Object> 有何不同?

【问题讨论】:

  • 请注意,术语“原始类型”是指对泛型类型的未参数化引用,例如ListList<?>。概念略有不同。
  • 只是指出List<Object> 不是原始类型,它的泛型类型是Object。原始类型是 List

标签: java generics unbounded-wildcard


【解决方案1】:

List<?>List<Object> 有何不同

主要区别在于第一行编译,而第二行不编译:

List<?> list = new ArrayList<String> ();
List<Object> list = new ArrayList<String> ();

但是,因为你不知道List&lt;?&gt;的泛型类型是什么,所以你不能使用它的参数化方法:

List<?> list = new ArrayList<String> ();
list.add("aString"); //does not compile - we don't know it is a List<String>
list.clear(); //this is fine, does not depend on the generic parameter type

至于与原始类型(没有泛型)的区别,下面的代码编译运行良好:

List list = new ArrayList<String> ();
list.add("aString");
list.add(10);

【讨论】:

  • 到目前为止知道了,但是原始类型对于 unbound 有何不同,为什么我们需要 unboud 通配符?你能用一些很好的例子来解释一下吗?
  • @Sandy IMO 的主要区别在于使用未绑定的通配符(例如在方法签名中)表示所讨论的方法知道泛型并将尊重该对象的泛型类型的承诺。对于原始类型,所有的赌注都没有了——您可以将一个数字添加到字符串列表中,如此处所示——混合通用代码和原始代码可能会导致 ClassCastExceptions 的风险。
  • @Sandy 我说明了 unbouded(第二个示例)和原始类型(第二个示例)之间的区别。他们有什么不清楚的地方?
  • @assylias 不错的答案。这有点晚了,但我有这种结构,List&lt;ScheduledFuture&lt;?&gt;&gt;。在一个包装好的执行器中,只是为了能够更好地管理任务。所以当我创建一个新的计划任务时,它必须被返回。否则服务无法取消任务。我看不出返回一个通用的ScheduledFuture&lt;?&gt; newScheduledTask() {} 而不仅仅是一个原始的ScheduledFuture newScheduledTask() {} 有任何意义。在这种情况下你会推荐什么?
  • @patrik 使用 &lt;?&gt; 强制编译器验证泛型安全性 - 如果您使用原始类型,编译器将不会检查任何内容并且它可能会产生一些隐蔽的错误(找不到示例不过现在)。因为差别不大,所以我会坚持使用&lt;?&gt;
【解决方案2】:

List&lt;?&gt;List&lt;Object&gt; 有何不同?

    List<Object> l1 = new ArrayList();
    List<?> l2 = new ArrayList();
    l1.add("Object");
    //l2.add("Object");   incorrect
    l2.add(null);

您只能将空值添加到List&lt;?&gt;

【讨论】:

    【解决方案3】:

    就我个人而言,我发现这个additional link from the Java tutorial on wildcards 很有帮助。

    我在List&lt;?&gt;List 之间看到的主要区别之一是前者只能用于读取其元素(除非您真的想添加null),后者允许(未选中)向其中添加任意类型的对象,可能会产生意想不到的副作用。

    【讨论】:

      【解决方案4】:

      有人可以用外行语言解释未绑定通配符和原始类型之间的区别吗?

      未绑定的通配符类型可以保持集合的类型不变,而原始类型不能。正如 Joshua Bloch 在他的 Effective Java 中所说,

      您可以将任何元素放入具有原始类型的集合中,这很容易破坏集合的类型不变量(如第 112 页的 unsafeAdd 方法所示);您不能将任何元素(除了 null)放入 Collection>.

      因此,只要将参数化类型列表分配给未绑定通配符类型列表,集合的类型不变性就会保持。

      List<String> list1 = new ArrayList();
      list1.add("foo");
      list1.add("bar");
      List<?> list2 = list1;
      

      如果你分配一个原始类型列表,它的元素是不同类型的,未绑定的通配符类型将不会保持集合的类型不变,因为该列表最初是变体类型.

      List list1 = new ArrayList();
      list1.add(1);
      list1.add("foo");
      List<?> list2 = list1;
      

      【讨论】:

        【解决方案5】:

        List 在方法签名中非常有用,可以调用从不需要类型参数的方法,例如,从列表中读取或旋转它。

        void someMethod(List<?> list) {
          list.clear();  // I will never add anything to the list in here
        }
        

        您将永远不会添加任何内容或以其他方式修改列表所包含的类型,因为您无法在具有此签名的方法中向列表添加任何内容,除了 null ,因此您永远不会破坏类型安全。 另一方面,您可以对原始列表执行任何操作,众所周知,这会导致类型安全违规。

        void someMethod2(List list) {
         list.add(new WeaselFurBrush());  
        }
        List list1 = new ArrayList<String>();
        someMethod2(list1);// oops
        

        【讨论】:

          猜你喜欢
          • 2021-10-27
          • 2013-09-26
          • 2017-07-16
          • 2011-04-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-06-12
          • 1970-01-01
          相关资源
          最近更新 更多