简体   繁体   English

Java泛型:有界通配符破坏日食

[英]java generics: bounded wildcard upsetting eclipse

There is an external interface named Shape (name changed, but you get the idea). 有一个名为Shape的外部接口(名称已更改,但是您知道了)。

I have a class that has a constructor that takes 我有一个带有需要的构造函数的类

Collection<? extends Shape>

This class is under test. 该课程正在测试中。 I am writing the unit tests. 我正在编写单元测试。 I want to create a Collection that extends Shape to provide to the constructor. 我想创建一个扩展Shape的Collection,以提供给构造函数。

I notice that no class in our project implements Shape, so I write an inner class: 我注意到我们项目中没有任何类实现Shape,所以我写了一个内部类:

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

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

Then I try to create my Collection ... 然后,我尝试创建我的收藏集...

private ArrayList<? extends Shape> shapes;
shapes = new ArrayList<Shape>();
shapes.add(new TestShape());

Eclipse however is unhappy with me ... 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)

Why? 为什么?

edit: Lots of editing because generics syntax makes SO unhappy 编辑:大量编辑,因为泛型语法使SO不满意

Let's imagine that we have TestShape implements Shape , and EvilShape also implements Shape . 假设我们有TestShape实现Shape ,而EvilShape也实现Shape Now imagine the following: 现在想象一下:

private ArrayList<? extends Shape> shapes; //OK
shapes = new ArrayList<EvilShape>(); // OK
authorities.add(new TestShape()); // Type safety issue

Clearly, such a thing won't work. 显然,这样的事情是行不通的。 Instead, do the following: 而是,请执行以下操作:

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

Or the following: 以下内容:

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

This will work, as you can add a subclass/implementation of Shape to a List<Shape> . 这将起作用,因为您可以将Shape的子类/实现添加到List<Shape> Using an exact type parameter and not a wildcard will prevent the following: 使用确切的类型参数而不使用通配符将防止以下情况:

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

The abbreviation PECS helps remember this: Producer Extends, Consumer Super. PECS的缩写有助于记住这一点:生产者扩展,超级消费者。 This comes from Joshua Bloch's Effective Java . 这来自Joshua Bloch的Effective Java

I have a class that has a constructor that takes 我有一个带有需要的构造函数的类

 Collection<? extends Shape> 

... ...

Then I try to create my Collection ... 然后,我尝试创建我的收藏集...

 private ArrayList<? extends Shape> shapes; 

I think one thing that may be confusing is that just because there is a method parameter like that ( Collection<? extends Shape> ), you don't need to create a variable like that. 我认为可能会引起混淆的一件事是,仅仅因为有一个这样的方法参数( Collection<? extends Shape> ),您不需要创建这样的变量。 Think of the purpose of a variable/parameter that uses wildcard generics (? extends X or ? super Y) as a way to write some "generic" code that will work with any value from a family of similar types rather than the usual "a value from a specific type". 想一想使用通配符泛型(?扩展X或?super Y)作为变量/参数的目的的一种方式,该方式可以编写一些“泛型”代码,该代码将与family of similar typesfamily of similar types任何值一起工作,而不是通常的“ a”特定类型的值”。

-- -

The perfect example of this is the signature for Collection.copy(): 完美的例子是Collection.copy()的签名:

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

This method allows me to pass an instance of List<Integer> and have its values copied into an instance of List<Number> . 此方法允许我传递List<Integer>的实例,并将其值复制到List<Number>的实例中。 It also allows me to copy List<Integer> into List<Integer> . 它还允许我将List<Integer>复制到List<Integer> It lets me use the same code with different types ("family of types"). 它使我可以将相同的代码用于不同的类型(“类型族”)。 I can test it with using wildcard generic variables at all: 我可以使用通配符泛型变量进行测试:

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

Collection.copy(src, dest);

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

-- -

The big tradeoff with using a variable or parameter like List<? super T> 使用诸如List<? super T> List<? super T> or List<? extends T> List<? super T>List<? extends T> List<? extends T> is that because its not a variable for a specific type, there are limitations on what you can assume about the instance it points to and therefore you are limited in what you can do with it. List<? extends T>在于,由于它不是特定类型的变量,因此您对它所指向的实例的假设有一定的限制,因此您只能使用它进行操作。

PECS is a tiny bit of an oversimplification, but it makes a great mnemonic: PECS = "producer extends, consumer super" means that a variable (or parameter) for a generic wildcard type that uses extends can only be used to "produce" values, ie you can safely only read from it, you can't add to it. PECS有点过分简化,但它有一个很大的助记符: PECS =“生产者扩展,消费者超级”表示使用extends的通用通配符类型的变量(或参数)只能用于“产生”值,即您只能安全地读取它,而不能添加它。 Similarly, a variable for a generic wildcard type that uses super can only be used to "consume" values - you can safely only feed it values (add), but there are limitations on what you can assume about the values that could be "read" from it. 同样,使用super的通用通配符类型的变量只能用于“消费”值-您可以安全地仅将其值(添加)提供给它,但是对于可以“读取”的值的假设存在限制“ 从中。

So, for your example, I would avoid using a variable with a wildcard generic at all, just pass in different List<> types to the constructor. 因此,对于您的示例,我完全避免使用带有通配符泛型的变量,只需将不同的List <>类型传递给构造函数。 If you must use a variable with a wildcard generic, that use a variable to the specific type to fill it because the variable with the wildcard generic that uses extends can't be used to fill it: 如果必须使用带有通配符泛型的变量,则使用特定类型的变量来填充它,因为带有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

-- -

You might find this question/answer useful: 您可能会发现此问题/答案很有用:

How can I add to List<? 如何添加到列表<? extends Number> data structures? 扩展Number>数据结构? - here's an example from it: -这是一个例子:

The wildcard declaration of List<? extends Number> foo3 List<? extends Number> foo3的通配符声明 List<? extends Number> foo3 means that the variable foo3 can hold any value from a family of types (rather than any value of a specific type). List<? extends Number> foo3意味着变量foo3可以保存一系列类型中的任何值(而不是特定类型的任何值)。 It means that any of these are legal assignments: 这意味着这些都是合法的转让:

 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 

So, given this, what type of object could you add to List foo3 that would be legal after any of the above possible ArrayList assignments: 因此,鉴于此,在上述任何可能的ArrayList分配之后,您可以将什么类型的对象添加到List foo3 ,这是合法的:

  • You can't add an Integer because foo3 could be pointing at a List<Double> . 您不能添加Integer因为foo3可能指向List<Double>
  • You can't add a Double because foo3 could be pointing at a List<Integer> . 您不能添加Double因为foo3可能指向List<Integer>
  • You can't add a Number because foo3 could be pointing at a List<Integer> . 您不能添加Number因为foo3可能指向List<Integer>

You can't add any object to List<? extends T> 您不能将任何对象添加到List<? extends T> List<? extends T> because you can't guarantee what kind of List it is really pointing to, so you can't guarantee that the object is allowed in that List . List<? extends T>因为您不能保证它真正指向的是哪种List ,因此不能保证该List允许该对象。 The only "guarantee" is that you can only read from it and you'll get a T or subclass of T . 唯一的“保证”是,你只能从它读,你会得到一个T的或子类T

This is a similar question/answer: 这是一个类似的问题/答案:

Difference between <? 之间的区别? super T> and <? 超级T>和<? extends T> in Java 在Java中扩展T>

If this is for a unit test, just make a collection of Shapes: 如果这是用于单元测试,则只需收集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);
    }
}

this compiles cleanly and is likely how the method is used in actual practice. 这样可以编译得很干净,并且很有可能在实际实践中使用该方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM