【问题标题】:How can I use a generic class with wildcard declaration?如何使用带有通配符声明的泛型类?
【发布时间】:2019-05-07 12:59:04
【问题描述】:

我的班级中有以下成员:

List<? extends SomeObject> list;

当我尝试这样做时:

list.add(list.get(0));

我明白了:

Test.java:7: error: no suitable method found for add(CAP#1)
        list.add(list.get(0));
            ^
    method Collection.add(CAP#2) is not applicable
      (argument mismatch; Object cannot be converted to CAP#2)
    method List.add(CAP#2) is not applicable
      (argument mismatch; Object cannot be converted to CAP#2)
  where CAP#1,CAP#2 are fresh type-variables:
    CAP#1 extends Object from capture of ? extends Object
    CAP#2 extends Object from capture of ? extends Object

我的问题有两个:

  1. 为什么不能编译?为什么我不能将get() 的结果传递给add()

  2. 我怎样才能在不使用强制转换的情况下以另一种方式实现这一目标?


我知道在&lt;T extends SomeObject&gt; 的方法中我不能只说:

T someObject = list.get(0);
list.add(someObject);

因为我的 T 可能是 ? 扩展之外的另一个扩展。

我也明白我不能说:

List<? extends SomeObject> list1;
List<? extends SomeObject> list2;
list1.add(list2.get(0));

但是由于 add 和 get 应该在 list.add(list.get(0)) 中使用相同的泛型类型,我不明白为什么编译器不接受它。


我真正需要的是

[something of type T where T is whatever was used to instantiate list] someObject = list.get(0);
list.add(someObject);

这样我以后可以

list.add(someObject);

我认为我不应该为我的整个班级模板化来实现这一点,不是吗?

class MyClass<T extends SomeObject> {
List<T> list;

然后是一个方法

T someObject = list.get(0);

当然可以,但是我的代码的其他部分搞砸了。

所以第一个问题是为什么这不起作用,第二个问题是最好的解决方法是什么?

【问题讨论】:

    标签: java generics wildcard


    【解决方案1】:

    我的问题是双重的,为什么我不能这样做:

    list.add(list.get(0));
    

    因为编译器不够聪明,无法知道您将 list 中的内容添加回 list。编译器不会认为 list.get(0)list 在评估后有任何关系:它只是 ? extends SomeObject 类型的“某些表达式”。

    为了解决这个问题,添加一个带有自己类型变量的方法:

    private <T> void addFirst(List<T> list) {
      list.add(list.get(0));
    }
    

    并将原来的 list.add(list.get(0)); 替换为以下内容的调用:

    addFirst(list);
    

    这只是在方法上定义了一个类型变量,不需要在类外可见,所以不需要类级别的类型变量。


    也许值得指出的是,这类似于Collections.swap method:使用set 而不是add,但是,从泛型的角度来看,它是一回事:

    @SuppressWarnings({"rawtypes", "unchecked"})
    public static void swap(List<?> list, int i, int j) {
        // instead of using a raw type here, it's possible to capture
        // the wildcard but it will require a call to a supplementary
        // private method
        final List l = list;
        l.set(i, l.set(j, l.get(i)));
    }
    

    这采用了一种技术上类型安全的方法,并且确实避免了强制转换;但这有点恶心,因为它使用原始类型。

    我想这只是出于向后兼容的原因。有机会再写一次,你可以像上面的addFirst 方法那样定义一个类型变量。

    【讨论】:

    • “我想这只是出于向后兼容的原因。”我想这是出于效率原因,以避免额外的函数调用。
    • @newacct 如果该方法首先被定义为static &lt;E&gt; void swap(List&lt;E&gt; list, ...),则不需要额外的方法调用。
    • 是的,但是有一个原则是,如果通配符版本的工作方式相同,API 不应声明类型变量,就像这里的情况一样,因为类型变量只在类型参数中使用一次单个参数。需要类型变量的原因是内部实现原因,不应在公共 API 中公开。
    • @newacct 所以,你现在说它不是是因为效率。
    • @newacct 虽然我在很大程度上同意如果不必要的话应该避免使用类型变量,但几乎没有任何令人惊讶的(或可避免的)交换方法需要获取和设置列表元素。我宁愿看到一个类型变量,并且确信代码正在由编译器进行类型检查,而不是看到原始类型,并且不得不花时间推理它是否真的安全。但是,你知道,这正是我个人风险偏好所在。
    【解决方案2】:

    当使用通配符时,我们应该遵循Java泛型和集合中介绍的The Get and Put Principle

    Get 和 Put 原则:当您只从 结构,当您只将值放入结构时使用super 通配符,不要使用 get 和 put 时都是通配符。

    在您的情况下,不要使用通配符,因为您既可以从列表中获取元素,也可以将元素放入列表中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-09-23
      • 1970-01-01
      • 2018-02-20
      • 2012-10-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多