【问题标题】:Java lower bound wildcardsJava 下界通配符
【发布时间】:2012-11-29 10:01:27
【问题描述】:

我正在努力解决这个问题,并想知道是否有人可以解释其中的原因。

我有三个班级:

class Angel {}
class Person extends Angel {}
class Employee extends Person {}

当我尝试执行这段代码时

public static void insertElements(List<? super Person> list){
    list.add(new Person());
    list.add(new Employee());
    list.add(new Angel());
}

我收到一个错误:

The method add(capture#5-of ? super Person) in the type List<capture#5-of ? super Person> is not applicable for the arguments (Angel)

我一直阅读文档的意思是 &lt;? super X &gt; 表示任何类型的 X 或 X 的超类(所以在我的情况下,Angel 是 Person 的超类)并且应该被允许?显然不是!

有人有简单的例子吗?

【问题讨论】:

  • 帮我写一下Employee、Person、Angel类之间的关系好吗?

标签: java wildcard


【解决方案1】:

您的直觉逻辑说“List&lt;? super Person&gt; 是一个 PersonPerson 的超类型的事物列表,所以我自然可以在其中添加一个 Angel” .这种解释是错误的。

声明List&lt;? super Person&gt; list 保证list 的类型允许将Person 的任何内容添加到列表中。由于Angel不是Person,这自然是编译器不允许的。考虑使用insertElements(new ArrayList&lt;Person&gt;) 调用您的方法。可以将Angel 添加到这样的列表中吗?绝对不是。

最好的推理方式是List&lt;? super Person&gt; 没有明确的类型:它是一个模式,描述了允许作为参数的一系列类型。将List&lt;Person&gt; 视为不是List&lt;? super Person&gt; 的子类型,而是与此模式匹配的类型。 List&lt;? super Person&gt; 上允许的操作是任何匹配类型上允许的操作。

【讨论】:

  • 感谢 Marko,您的回答非常有道理。那么我可以跟进一个问题......有什么区别?超级和?延伸。我总是读书?正如您所描述的那样延伸到ezxactly?极好的。 (即:任何存在的人)
  • List&lt;? extends Person&gt; 是人员列表或更窄的列表。在这种情况下,您可以保证从中 get 一个 Person 的东西,但是您将无法向其中添加任何自己的东西,因为您不知道列表有多窄.
  • 为了进行类型擦除工作,编译器必须将通配符具体化为特定的边界类型。它使用推理。第 1 步:编译器推断它是 List&lt;Person&gt;。第 2 步:编译器验证 Listadd 方法正在获取 PersonPerson 的子类型。这里的线索是编译器说CAP#1 extends Object super: Person from capture of ? super Person。它可以帮助我将“超级对象:Person”视为Person 是其超类型的对象,或者更简单地说,Person 类型的对象。天使不是人,所以编译失败。
  • 您声明“声明 List super Person> list 保证 list 将是允许任何人的类型”,但不是 List。我觉得这非常令人困惑,根本没有明显的区别。
  • @AndrewS List&lt;Boy&gt; 符合 List&lt;? extends Person&gt;。但我无法将Girl 添加到该列表中。显然,List&lt;? extends Person&gt; 并不能保证我可以添加任何 Person 到。
【解决方案2】:

对我来说,这些答案都不够清楚,尽管它们对我有所帮助。看了很多,我的意思很多,我终于想出了通配符最简单的解释:

public class CatTest {

    public class Animal {}
    public class Cat extends Animal {}
    public class MyCat extends Cat {}
    public class Dog extends Animal {}

    public static void main(String[] args) {
        List<Animal> animalList = new ArrayList<>();
        List<Cat> catList = new ArrayList<>();
        List<MyCat> myCatList = new ArrayList<>();
        List<Dog> dogList = new ArrayList<>();

        CatTest catTest = new CatTest();
        // Here you are trying to add a MyCat instance (in the addMethod we create a MyCat instance). MyCat is a Cat, Cat is an Animal, therefore MyCat is an Animal.
        // So you can add a new MyCat() to a list of List<Animal> because it's a list of Animals and MyCat IS an Animal.
        catTest.addMethod(animalList);

        // Here you are trying to add a MyCat instance (in the addMethod we create a MyCat instance). MyCat is a Cat. 
        // So you can add a new MyCat() to a list of List<Cat> because it is a list of Cats, and MyCat IS a Cat
        catTest.addMethod(catList);

        // Here you are trying to add a MyCat instance (in the addMethod we create a MyCat instance). MyCat is a Cat.
        // Everything should work but the problem here is that you restricted (bounded) the type of the lists to be passed to the method to be of
        // a type that is either "Cat" or a supertype of "Cat". While "MyCat" IS a "Cat". It IS NOT a supertype of "Cat". Therefore you cannot use the method
        catTest.addMethod(myCatList); // Doesn't compile

        // Here you are adding a MyCat instance (in the addMethod we create a MyCat instance). MyCat is a Cat. 
        // You cannot call the method here, because "Dog" is not a "Cat" or a supertype of "Cat"
        catTest.addMethod(dogList); // Doesn't compile
    }

    public void addMethod(List<? super Cat> catList) {
        // Adding MyCat works since MyCat is a subtype of whatever type of elements catList contains
        // (for example Cat, Animal, or Object)
        catList.add(new MyCat());
        System.out.println("Cat added");
    }
}

最后,这些是结论:

使用通配符时,通配符适用于作为参数传递给方法的列表类型不适用于类型 元素 当您尝试将元素添加到列表中时。在示例中,您会收到以下编译错误:

CatTest 类型中的方法 addMethod(List) 不适用于参数 (List)

如您所见,错误与方法签名有关,与方法主体无关。因此,您只能传递“Cat”或“Cat”的超类型 (List&lt;Animal&gt;, List&lt;Cat&gt;) 的元素列表

一旦你传递了一个包含特定类型元素的列表,你可以只添加元素到列表中,这些元素要么是“Cat”,要么是“Cat”的子类型,也就是说,它当您拥有一组元素时,其行为一如既往!您不能将“Animal”添加到“Cat”列表中。正如我之前所说,通配符不适用于元素本身,限制仅适用于“列表”。现在,这是为什么呢?因为简单、明显、众所周知的原因:

Animal animal = new Dog();

如果您可以在“猫”列表中添加“动物”,您也可以添加“狗”(“狗”是“动物”),但它不是“猫”。

【讨论】:

  • 强调就够了,不用大写。
  • 你有多大胆
【解决方案3】:

像我一直做的那样有一个严格的理解,引入List&lt;? super Person&gt; 是为了确保List&lt;Person&gt;List&lt;Angel&gt; 都可以作为参数传入,这是upper bounded wildcard 提供的灵活性。

但是在方法内,在您的情况下,只有PersonPerson 的子类型可以插入到列表中,以确保列表始终包含有效实例。

例如如果您传入Person 的列表,则只能插入EmployeePerson,而Employee 将被自动类型转换为Person

【讨论】:

  • 1. List&lt;? super Person&gt; 不是上限通配符;它是下界通配符。 2. 不能将Angel(Person的子类型)实例插入到方法内的列表中。您只能插入PersonEmployee 实例或null 值。
【解决方案4】:

反过来想:对于绑定在List&lt;? super Person&gt; 上的类型,List&lt;Person&gt; 显然是参数的有效类型。 如果编译器允许您的代码,它将允许您将Angel 类型的内容插入List&lt;Person&gt;

【讨论】:

  • 是的,这就是他想要做的,所以解释一下为什么,不仅仅是那行不通。
【解决方案5】:

通配符基本上允许您在编写时指定参数的类型范围

public static void insertElements(List&lt;? super Person&gt; list),编译器将允许您传递 Person 或其任何超类型的列表。让我们看一个具体的例子

List<Object> objects = new ArrayList<>();
List<Angel> angels = new ArrayList<>();
List<Person> persons = new ArrayList<>();
List<Employee> employees = new ArrayList<>();

insertElements(objects);      // Works fine, Object is super type of every type
insertElements(angels);       // Works fine, Angel is super type of Person
insertElements(persons);      // Works fine, Person itself
insertElements(employees);    // Error, Employee is neither Person itself nor its a super type of Person

现在让我们看看为什么会出现这个错误

从上面的代码我们知道你可以将人员、天使或对象的列表传递给insertElements,它会正常工作,现在假设你调用带有人员列表的方法。所以列表现在是List&lt;Person&gt; 而你是list.add(Angel());

这实质上意味着您正在尝试执行 Person person = new Angel(); 此分配与类型不兼容,您会收到错误消息。

【讨论】:

    猜你喜欢
    • 2019-11-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-12
    • 2019-07-07
    相关资源
    最近更新 更多