java泛型:有界通配符令人沮丧的日食

时间:2014-05-27 20:41:33

标签: java eclipse generics

有一个名为Shape的外部接口(名称已更改,但您明白了。)

我有一个具有构造函数的类

Collection<? extends Shape>

本课程正在测试中。我正在写单元测试。我想创建一个扩展Shape的Collection,以提供给构造函数。

我注意到我们项目中没有类实现Shape,所以我写了一个内部类:

private class TestShape implements Shape {
    private static final long serialVersionUID = 1L;

    @Override
    public String getAuthority() {
        return "Foo";
    }
}

然后我尝试创建我的收藏......

private ArrayList<? extends Shape> shapes;
shapes = new ArrayList<Shape>();
shapes.add(new TestShape());
然而,Eclipse对我不满意......

The method add(capture#1-of ? extends Shape) in the type ArrayList<capture#1-of ? extends Shape> is not applicable for the arguments (Test.TestShape)

为什么呢?

编辑:大量编辑,因为泛型语法使得SO不快乐

3 个答案:

答案 0 :(得分:4)

我们假设我们TestShape实现了Shape,而EvilShape也实现了Shape。现在想象如下:

private ArrayList<? extends Shape> shapes; //OK
shapes = new ArrayList<EvilShape>(); // OK
authorities.add(new TestShape()); // Type safety issue
显然,这样的事情是行不通的。相反,请执行以下操作:

private ArrayList<Shape> shapes; //OK
shapes = new ArrayList<Shape>(); // OK
authorities.add(new TestShape()); // OK

以下内容:

private ArrayList<? super Shape> shapes; //OK
shapes = new ArrayList<Shape>(); // Or, for that matter, an `ArrayList<Object>`.
authorities.add(new TestShape()); // OK

这将有效,因为您可以将Shape的子类/实现添加到List<Shape>。使用精确类型参数而不是通配符将阻止以下操作:

private ArrayList<Shape> shapes; //OK
shapes = new ArrayList<EvilShape>(); // DANGER! Java does not allow this

缩写 PECS 有助于记住这一点:Producer Extends,Consumer Super。这来自Joshua Bloch的 Effective Java

答案 1 :(得分:0)

  

我有一个具有构造函数的类

Collection<? extends Shape>
     

...

     

然后我尝试创建我的收藏......

private ArrayList<? extends Shape> shapes;

我认为可能令人困惑的一件事是,因为有一个类似的方法参数(Collection<? extends Shape>),你不需要像这样创建一个变量。想想使用通配符泛型(?extends X或?super Y)的变量/参数的用途,作为编写一些“通用”代码的方法,该代码可以使用来自family of similar types而不是通常的任何值。来自特定类型的值“。

-

这方面的完美示例是Collection.copy()的签名:

public static <T> void copy(List<? super T> dest,List<? extends T> src)

此方法允许我传递List<Integer>的实例,并将其值复制到List<Number>的实例中。它还允许我将List<Integer>复制到List<Integer>。它允许我使用不同类型的相同代码(“类型族”)。我可以使用通配符泛型变量来测试它:

List<Integer> src = new ArrayList<>();
src.add(1);
List<Number> dest = new ArrayList<>();

Collection.copy(src, dest);

assert(src.get(0), dest.get(0));

-

使用List<? super T>List<? extends T>等变量或参数的重大权衡因为它不是特定类型的变量,因此存在限制您可以对它所指向的实例有什么假设,因此您可以用它来做什么。

PECS只是过于简单化,但是它有一个很好的助记符:PECS =“生产者扩展,消费者超级”意味着通用通配符类型的变量(或参数)用extends只能用于“生成”值,即你只能安全地读取它,你不能添加它。类似地,使用super的通用通配符类型的变量只能用于“消耗”值 - 您可以安全地仅为其提供值(添加),但是对于可以假设的值,存在限制可以从中“读”出来。

因此,对于您的示例,我会避免使用带有通配符泛型的变量,只需传入不同的List&lt;&gt;构造函数的类型。如果必须使用带通配符通用的变量,那么使用特定类型的变量来填充它,因为使用extends的通配符通用的变量不能用于填充它:

private ArrayList<Shape> shapesSpecific;
private ArrayList<? extends Shape> shapesExtends;
shapesExtends = shapesSpecific = new ArrayList<Shape>();

shapesSpecific.add(new TestShape());   // fine
//shapesExtends.add(new TestShape());  // error - but the line above added a value already

// Another option - create it already filled:
// shapeExtends = Arrays.asList(new TestShape());

new Foo(shapesSpecific);  // fine
new Foo(shapesExtends);   // fine

-

您可能会发现此问题/答案很有用:

How can I add to List<? extends Number> data structures? - 这是一个例子:

  

List<? extends Number> foo3的通配符声明意味着   变量foo3可以保存一系列类型的任何值(相反   比任何特定类型的值)。这意味着其中任何一个都是   法律任务:

List<? extends Number> foo3 = new ArrayList<Number>;  // Number "extends" Number
List<? extends Number> foo3 = new ArrayList<Integer>; // Integer extends Number
List<? extends Number> foo3 = new ArrayList<Double>;  // Double extends Number
     

因此,鉴于此,您可以向List foo3添加哪种类型的对象   在上述任何可能的ArrayList之后是合法的   分配:

     
      
  • 您无法添加Integer,因为foo3可能指向List<Double>
  •   
  • 您无法添加Double,因为foo3可能指向List<Integer>
  •   
  • 您无法添加Number,因为foo3可能指向List<Integer>
  •   
     

您无法向List<? extends T>添加任何对象,因为您无法保证它真正指向的是哪种List,所以您不能   保证List中允许该对象。唯一的   “保证”是你只能阅读它,你会得到T或。{   T的子类。

这是一个类似的问题/答案:

Difference between <? super T> and <? extends T> in Java

答案 2 :(得分:0)

如果是单元测试,只需创建一个Shapes集合:

import java.util.ArrayList;
import java.util.Collection;

public class test {
    public interface Shape {
        String getAuthority();

    }

    private static class TestShape implements Shape {

        @Override
        public String getAuthority() {
            return "Foo";
        }
    }

    public static void method(Collection<? extends Shape> shapes){

    }

    public static void main(String ... args){
        ArrayList<Shape> shapes;
        shapes = new ArrayList<Shape>();
        shapes.add(new TestShape());
        method(shapes);
    }
}

这可以干净地编译,很可能是在实际操作中如何使用该方法。