是列表< dog> List<动物的子类为什么Java仿制药不是隐式多态性的?

发布于 2025-01-28 04:37:03 字数 556 浏览 4 评论 0 原文

我对Java仿制药如何处理继承 /多态性有些困惑。

假设以下层次结构 -

动物(parent)

- cat (儿童),

所以假设我有一种方法 dosomething(list< Animal&gt ;)。根据所有继承和多态性的规则,我认为 list< dog> is a list< andial< andial> list< cat> a list< andial> - 因此,可以将任何一种都传递给此方法。不是这样。如果我想实现这种行为,我必须明确地告诉该方法,通过说 dosomething(列表<?

我知道这是爪哇的行为。我的问题是为什么?为什么多态性通常是隐式的,但是在仿制药方面必须指定它?

I'm a bit confused about how Java generics handle inheritance / polymorphism.

Assume the following hierarchy -

Animal (Parent)

Dog - Cat (Children)

So suppose I have a method doSomething(List<Animal> animals). By all the rules of inheritance and polymorphism, I would assume that a List<Dog> is a List<Animal> and a List<Cat> is a List<Animal> - and so either one could be passed to this method. Not so. If I want to achieve this behavior, I have to explicitly tell the method to accept a list of any subclass of Animal by saying doSomething(List<? extends Animal> animals).

I understand that this is Java's behavior. My question is why? Why is polymorphism generally implicit, but when it comes to generics it must be specified?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(19

蓦然回首 2025-02-04 04:37:04

您要寻找的称为 “ noreferrer”> covariant类型参数参数。这意味着,如果可以在方法中代替另一种对象(例如, Animal 可以用 dog )代替,则使用这些对象也适用于表达(因此列表&lt; andial&gt; 可以用 list&lt; dog&gt; 替换)。问题在于,对于一​​般的可变列表而言,协方差并不安全。假设您有一个列表&lt; dog&gt; ,并且它被用作 list&lt; andial&gt; 。当您尝试将CAT添加到此 list&lt; andial&gt; 时,会发生什么?自动允许类型参数为协变量会破坏类型系统。

添加语法以允许将类型参数指定为协变量是有用的,它避免了?在方法声明中扩展了foo ,但这确实增加了额外的复杂性。

What you are looking for is called covariant type parameters. This means that if one type of object can be substituted for another in a method (for instance, Animal can be replaced with Dog), the same applies to expressions using those objects (so List<Animal> could be replaced with List<Dog>). The problem is that covariance is not safe for mutable lists in general. Suppose you have a List<Dog>, and it is being used as a List<Animal>. What happens when you try to add a Cat to this List<Animal> which is really a List<Dog>? Automatically allowing type parameters to be covariant breaks the type system.

It would be useful to add syntax to allow type parameters to be specified as covariant, which avoids the ? extends Foo in method declarations, but that does add additional complexity.

一向肩并 2025-02-04 04:37:04

list&lt; dog&gt; 不是 list&lt; andial&gt; 的原因是,例如,您可以将 cat 插入<<代码> list&lt; andial&gt; ,但不在 list&lt; dog&gt; ...您可以使用通配符使仿制药在可能的情况下更加可扩展;例如,从 list&lt; dog 读取类似于从 list&lt; andial&gt; 读取的读物,但不写作。

generics in Java语言中a href =“ http://java.sun.com/docs/books/tutorial/java/java/generics/index.html” rel =“ noreferrer”>“ noreferrer”> java tutorials of java tutorials的部分深入解释了某些事物为何或不允许仿制药允许的原因。

The reason a List<Dog> is not a List<Animal>, is that, for example, you can insert a Cat into a List<Animal>, but not into a List<Dog>... you can use wildcards to make generics more extensible where possible; for example, reading from a List<Dog> is the similar to reading from a List<Animal> -- but not writing.

The Generics in the Java Language and the Section on Generics from the Java Tutorials have a very good, in-depth explanation as to why some things are or are not polymorphic or permitted with generics.

时光暖心i 2025-02-04 04:37:04

我认为应该将一个点添加到其他 /1593077“>答案提及是

list&lt; dog&gt; 不是-a list&lt; andial&gt; java

也是正确的,也确实如此

英语中的动物清单(在合理的解释下)

即OP的直觉的工作方式 - 当然是完全有效的 - 是后者的句子。但是,如果我们应用这种直觉,我们会得到一种在其类型系统中不符合Java式的语言:假设我们的语言确实允许将猫添加到我们的狗列表中。那意味着什么?这意味着该清单不再是狗的清单,而只是动物清单。以及哺乳动物的清单,以及四边形的清单。

换句话说:java中的 list&lt; dog&gt; 并不意味着英语中的“狗名单”,而是“狗名单,除了狗之外别无其他”。

更一般而言, op的直觉使自己倾向于一种语言,在这种语言中,对象可以更改其类型,或者更确切地说,对象的类型是其价值的(动态)函数。

A point I think should be added to what other answers mention is that while

List<Dog> isn't-a List<Animal> in Java

it is also true that

A list of dogs is-a list of animals in English (under a reasonable interpretation)

The way the OP's intuition works - which is completely valid of course - is the latter sentence. However, if we apply this intuition we get a language that is not Java-esque in its type system: Suppose our language does allow adding a cat to our list of dogs. What would that mean? It would mean that the list ceases to be a list of dogs, and remains merely a list of animals. And a list of mammals, and a list of quadrapeds.

To put it another way: A List<Dog> in Java does not mean "a list of dogs" in English, it means "a list of dogs and nothing other than dogs".

More generally, OP's intuition lends itself towards a language in which operations on objects can change their type, or rather, an object's type(s) is a (dynamic) function of its value.

蓝戈者 2025-02-04 04:37:04

我想说的是,仿制药的全部要点是它不允许这样做。考虑使用数组的情况,该数组确实允许这种类型的协方差:

  Object[] objects = new String[10];
  objects[0] = Boolean.FALSE;

该代码会编译良好,但会引发运行时错误( java.lang.ArrayStoreException:java.lang.boolean 在第二行中)。它不是Typesafe。仿制药的重点是添加编译时类型安全性,否则您只需坚持没有仿制药的普通类。

现在有时候您需要更加灵活,这就是?超级类?扩展类是为了。前者是当您需要插入类型 Collection (例如)时,而后者则用于以一种安全的方式从中读取时。但是,同时进行这两者的唯一方法是拥有特定类型。

I would say the whole point of Generics is that it doesn't allow that. Consider the situation with arrays, which do allow that type of covariance:

  Object[] objects = new String[10];
  objects[0] = Boolean.FALSE;

That code compiles fine, but throws a runtime error (java.lang.ArrayStoreException: java.lang.Boolean in the second line). It is not typesafe. The point of Generics is to add the compile time type safety, otherwise you could just stick with a plain class without generics.

Now there are times where you need to be more flexible and that is what the ? super Class and ? extends Class are for. The former is when you need to insert into a type Collection (for example), and the latter is for when you need to read from it, in a type safe manner. But the only way to do both at the same time is to have a specific type.

浴红衣 2025-02-04 04:37:04

要了解问题,与阵列进行比较很有用。

list&lt; dog&gt; 不是 列表的子类
狗[] 动物[] 的子类。

数组为 reififable> reifiable 和covariant
reififable> reififiable 意味着他们的类型信息在运行时可以在运行时提供。
因此,阵列可提供运行时类型的安全性,但不提供编译时类型的安全性。

    // All compiles but throws ArrayStoreException at runtime at last line
    Dog[] dogs = new Dog[10];
    Animal[] animals = dogs; // compiles
    animals[0] = new Cat(); // throws ArrayStoreException at runtime

反之亦然:
仿制药为擦除 and noffariant
因此,仿制药无法提供运行时类型的安全性,但是它们提供了编译时类型的安全性。
在下面的代码中,如果仿制药是协变量的,则可以制作堆污染 3。

    List<Dog> dogs = new ArrayList<>();
    List<Animal> animals = dogs; // compile-time error, otherwise heap pollution
    animals.add(new Cat());

To understand the problem it's useful to make comparison to arrays.

List<Dog> is not subclass of List<Animal>.
But Dog[] is subclass of Animal[].

Arrays are reifiable and covariant.
Reifiable means their type information is fully available at runtime.
Therefore arrays provide runtime type safety but not compile-time type safety.

    // All compiles but throws ArrayStoreException at runtime at last line
    Dog[] dogs = new Dog[10];
    Animal[] animals = dogs; // compiles
    animals[0] = new Cat(); // throws ArrayStoreException at runtime

It's vice versa for generics:
Generics are erased and invariant.
Therefore generics can't provide runtime type safety, but they provide compile-time type safety.
In the code below if generics were covariant it will be possible to make heap pollution at line 3.

    List<Dog> dogs = new ArrayList<>();
    List<Animal> animals = dogs; // compile-time error, otherwise heap pollution
    animals.add(new Cat());
烟凡古楼 2025-02-04 04:37:04

其他人已经做出了不错的工作,以解释为什么您不能仅仅将后代列表列入超级阶级列表。

但是,许多人访问了这个问题,以寻求解决方案。

因此,由于Java版本10的方法如下:(

注意:S = SuperClass)

List<S> supers = List.copyOf( descendants );

如果它完全安全,或者如果演员不安全,则此功能将执行铸件。最终的列表不可修道。

有关深入的解释(考虑到其他答案中提到的潜在陷阱)有关)请参阅相关问题和我的2022答案: https://stackoverflow.com/a/72195980/773113

Others have done a decent job of explaining why you cannot just cast a list of descendant to list of superclass.

However, many people visit this question looking for a solution.

So, the solution to this problem since Java version 10 is as follows:

(Note: S = superclass)

List<S> supers = List.copyOf( descendants );

This function will do a cast if it is perfectly safe to do so, or a copy if a cast would not be safe. The resulting list is unmodifiable.

For an in-depth explanation (which takes into consideration the potential pitfalls mentioned by other answers here) see related question and my 2022 answer to it: https://stackoverflow.com/a/72195980/773113

信仰 2025-02-04 04:37:04

这里给出的答案并没有完全说服我。因此,我做了另一个例子。

public void passOn(Consumer<Animal> consumer, Supplier<Animal> supplier) {
    consumer.accept(supplier.get());
}

听起来不错,不是吗?但是,您只能通过消费者 s和供应商 <代码>动物 s。如果您有哺乳动物消费者,但是 duck 供应商,尽管两者都是动物,但它们不应适合。为了不允许这样做,添加了其他限制。

我们必须定义使用我们使用的类型之间的关系,而不是上述。

E. g。,

public <A extends Animal> void passOn(Consumer<A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

确保我们只能使用为消费者提供正确类型的对象类型的供应商。

OTOH,我们也可以

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<A> supplier) {
    consumer.accept(supplier.get());
}

在其他方面做的地方做:我们定义供应商的类型,并限制它可以将其放入消费者中。

我们甚至可以

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

在哪里做,拥有直观关系 life - &gt; 动物 - &gt; 哺乳动物 - &gt; dog cat 等,我们甚至可以将哺乳动物放入 life 消费者中,而不是<代码>字符串进入 life 消费者。

The answers given here didn't fully convince me. So instead, I make another example.

public void passOn(Consumer<Animal> consumer, Supplier<Animal> supplier) {
    consumer.accept(supplier.get());
}

sounds fine, doesn't it? But you can only pass Consumers and Suppliers for Animals. If you have a Mammal consumer, but a Duck supplier, they should not fit although both are animals. In order to disallow this, additional restrictions have been added.

Instead of the above, we have to define relationships between the types we use.

E. g.,

public <A extends Animal> void passOn(Consumer<A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

makes sure that we can only use a supplier which provides us the right type of object for the consumer.

OTOH, we could as well do

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<A> supplier) {
    consumer.accept(supplier.get());
}

where we go the other way: we define the type of the Supplier and restrict that it can be put into the Consumer.

We even can do

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

where, having the intuitive relations Life -> Animal -> Mammal -> Dog, Cat etc., we could even put a Mammal into a Life consumer, but not a String into a Life consumer.

祁梦 2025-02-04 04:37:04

这种行为的基础逻辑是 generics 遵循类型擦除的机制。因此,在运行时,如果识别 collection>的类型,您无法使用 collection> arrays 没有这种擦除过程。因此,回到您的问题...

因此,假设有一种方法如下:

add(List<Animal>){
    //You can add List<Dog or List<Cat> and this will compile as per rules of polymorphism
}

现在,如果Java允许呼叫者添加类型的动物列表到此方法输入擦除。虽然在数组中,您将获得此类场景的运行时间例外...

因此,本质上实现了此行为,因此不能将错误的东西添加到集合中。现在,我相信存在类型的擦除,以便在没有仿制药的情况下与传统的爪哇兼容。

The basis logic for such behavior is that Generics follow a mechanism of type erasure. So at run time you have no way if identifying the type of collection unlike arrays where there is no such erasure process. So coming back to your question...

So suppose there is a method as given below:

add(List<Animal>){
    //You can add List<Dog or List<Cat> and this will compile as per rules of polymorphism
}

Now if java allows caller to add List of type Animal to this method then you might add wrong thing into collection and at run time too it will run due to type erasure. While in case of arrays you will get a run time exception for such scenarios...

Thus in essence this behavior is implemented so that one cannot add wrong thing into collection. Now I believe type erasure exists so as to give compatibility with legacy java without generics....

高冷爸爸 2025-02-04 04:37:04

子类别为不变用于参数化类型。即使是艰难的类 dog Animal 的子类型,参数化类型 list&lt; dog&gt; 不是 list&lt; andial&gt的子类型; 。相比之下, covariant”> covariant”>大批
类型 dog [] 动物[] 的子类型。

不变亚型可确保不会违反Java强制执行的类型约束。考虑@Jon Skeet给出的以下代码:

List<Dog> dogs = new ArrayList<Dog>(1);
List<Animal> animals = dogs;
animals.add(new Cat()); // compile-time error
Dog dog = dogs.get(0);

如@Jon Skeet所述,此代码是非法的,因为否则,当狗预期时,它会通过返回猫来违反类型的约束。

将上述与数组的类似代码进行比较是有启发性的。

Dog[] dogs = new Dog[1];
Object[] animals = dogs;
animals[0] = new Cat(); // run-time error
Dog dog = dogs[0];

该代码是合法的。但是,抛出 array商店exception
AN阵列在运行时运行其类型,JVM可以强制执行
协变量的类型安全性。

要进一步理解这一点,让我们看一下以下类的 javap 生成的字节码:

import java.util.ArrayList;
import java.util.List;

public class Demonstration {
    public void normal() {
        List normal = new ArrayList(1);
        normal.add("lorem ipsum");
    }

    public void parameterized() {
        List<String> parameterized = new ArrayList<>(1);
        parameterized.add("lorem ipsum");
    }
}

使用命令 javap -c示例,这显示了以下java bytecode:

Compiled from "Demonstration.java"
public class Demonstration {
  public Demonstration();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void normal();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: iconst_1
       5: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
       8: astore_1
       9: aload_1
      10: ldc           #4                  // String lorem ipsum
      12: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      17: pop
      18: return

  public void parameterized();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: iconst_1
       5: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
       8: astore_1
       9: aload_1
      10: ldc           #4                  // String lorem ipsum
      12: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      17: pop
      18: return
}

观察翻译后的方法守则是相同的。编译器将每种参数化类型替换为擦除 。该属性至关重要,这意味着它没有向后兼容。

总之,参数化类型无法运行时安全性,因为编译器通过其擦除代替了每种参数化类型。这使参数化类型无非是句法糖。

Subtyping is invariant for parameterized types. Even tough the class Dog is a subtype of Animal, the parameterized type List<Dog> is not a subtype of List<Animal>. In contrast, covariant subtyping is used by arrays, so the array
type Dog[] is a subtype of Animal[].

Invariant subtyping ensures that the type constraints enforced by Java are not violated. Consider the following code given by @Jon Skeet:

List<Dog> dogs = new ArrayList<Dog>(1);
List<Animal> animals = dogs;
animals.add(new Cat()); // compile-time error
Dog dog = dogs.get(0);

As stated by @Jon Skeet, this code is illegal, because otherwise it would violate the type constraints by returning a cat when a dog expected.

It is instructive to compare the above to analogous code for arrays.

Dog[] dogs = new Dog[1];
Object[] animals = dogs;
animals[0] = new Cat(); // run-time error
Dog dog = dogs[0];

The code is legal. However, throws an array store exception.
An array carries its type at run-time this way JVM can enforce
type safety of covariant subtyping.

To understand this further let's look at the bytecode generated by javap of the class below:

import java.util.ArrayList;
import java.util.List;

public class Demonstration {
    public void normal() {
        List normal = new ArrayList(1);
        normal.add("lorem ipsum");
    }

    public void parameterized() {
        List<String> parameterized = new ArrayList<>(1);
        parameterized.add("lorem ipsum");
    }
}

Using the command javap -c Demonstration, this shows the following Java bytecode:

Compiled from "Demonstration.java"
public class Demonstration {
  public Demonstration();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void normal();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: iconst_1
       5: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
       8: astore_1
       9: aload_1
      10: ldc           #4                  // String lorem ipsum
      12: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      17: pop
      18: return

  public void parameterized();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: iconst_1
       5: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
       8: astore_1
       9: aload_1
      10: ldc           #4                  // String lorem ipsum
      12: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      17: pop
      18: return
}

Observe that the translated code of method bodies are identical. Compiler replaced each parameterized type by its erasure. This property is crucial meaning that it did not break backwards compatibility.

In conclusion, run-time safety is not possible for parameterized types, since compiler replaces each parameterized type by its erasure. This makes parameterized types are nothing more than syntactic sugar.

孤星 2025-02-04 04:37:04

如果您确定列表项目是给定的超级类型的子类,则可以使用此方法投射列表:

(List<Animal>) (List<?>) dogs

当您想通过构造函数内部或迭代列表时,这很有用。

If you are sure that the list items are subclasses of that given super type, you can cast the list using this approach:

(List<Animal>) (List<?>) dogs

This is usefull when you want to pass the list inside of a constructor or iterate over it.

我的痛♀有谁懂 2025-02-04 04:37:04

答案以及其他答案是正确的。我将通过我认为会有所帮助的解决方案来添加这些答案。我认为这在编程中经常出现。要注意的一件事是,对于集合(列表,集合等),主要问题是添加到集合中。那就是事情崩溃的地方。即使删除也可以。

在大多数情况下,我们可以使用 Collection&lt;?扩展t&gt; 而不是 collection&lt; t&gt; ,这应该是首选。但是,我发现这样做并不容易的情况。关于这是否始终是最好的事情,这是有争议的。我在这里介绍了一个downcastCollection,可以将 Collection进行转换?将T&gt; 扩展到集合&lt; t&gt; (我们可以在使用标准方法时使用列表,set,navigableset等定义相似的类,非常不便。以下是如何使用它的示例(我们还可以使用 Collection&lt;在这种情况下,扩展了对象&gt; ,但是我可以简单地使用DowncastCollection说明它。

/**Could use Collection<? extends Object> and that is the better choice. 
* But I am doing this to illustrate how to use DownCastCollection. **/

public static void print(Collection<Object> col){  
    for(Object obj : col){
    System.out.println(obj);
    }
}
public static void main(String[] args){
  ArrayList<String> list = new ArrayList<>();
  list.addAll(Arrays.asList("a","b","c"));
  print(new DownCastCollection<Object>(list));
}

现在,类:

import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class DownCastCollection<E> extends AbstractCollection<E> implements Collection<E> {
private Collection<? extends E> delegate;

public DownCastCollection(Collection<? extends E> delegate) {
    super();
    this.delegate = delegate;
}

@Override
public int size() {
    return delegate ==null ? 0 : delegate.size();
}

@Override
public boolean isEmpty() {
    return delegate==null || delegate.isEmpty();
}

@Override
public boolean contains(Object o) {
    if(isEmpty()) return false;
    return delegate.contains(o);
}
private class MyIterator implements Iterator<E>{
    Iterator<? extends E> delegateIterator;

    protected MyIterator() {
        super();
        this.delegateIterator = delegate == null ? null :delegate.iterator();
    }

    @Override
    public boolean hasNext() {
        return delegateIterator != null && delegateIterator.hasNext();
    }

    @Override
    public  E next() {
        if(!hasNext()) throw new NoSuchElementException("The iterator is empty");
        return delegateIterator.next();
    }

    @Override
    public void remove() {
        delegateIterator.remove();

    }

}
@Override
public Iterator<E> iterator() {
    return new MyIterator();
}



@Override
public boolean add(E e) {
    throw new UnsupportedOperationException();
}

@Override
public boolean remove(Object o) {
    if(delegate == null) return false;
    return delegate.remove(o);
}

@Override
public boolean containsAll(Collection<?> c) {
    if(delegate==null) return false;
    return delegate.containsAll(c);
}

@Override
public boolean addAll(Collection<? extends E> c) {
    throw new UnsupportedOperationException();
}

@Override
public boolean removeAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.removeAll(c);
}

@Override
public boolean retainAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.retainAll(c);
}

@Override
public void clear() {
    if(delegate == null) return;
        delegate.clear();

}

}

The answer as well as other answers are correct. I am going to add to those answers with a solution that I think will be helpful. I think this comes up often in programming. One thing to note is that for Collections (Lists, Sets, etc.) the main issue is adding to the Collection. That is where things break down. Even removing is OK.

In most cases, we can use Collection<? extends T> rather then Collection<T> and that should be the first choice. However, I am finding cases where it is not easy to do that. It is up for debate as to whether that is always the best thing to do. I am presenting here a class DownCastCollection that can take convert a Collection<? extends T> to a Collection<T> (we can define similar classes for List, Set, NavigableSet,..) to be used when using the standard approach is very inconvenient. Below is an example of how to use it (we could also use Collection<? extends Object> in this case, but I am keeping it simple to illustrate using DownCastCollection.

/**Could use Collection<? extends Object> and that is the better choice. 
* But I am doing this to illustrate how to use DownCastCollection. **/

public static void print(Collection<Object> col){  
    for(Object obj : col){
    System.out.println(obj);
    }
}
public static void main(String[] args){
  ArrayList<String> list = new ArrayList<>();
  list.addAll(Arrays.asList("a","b","c"));
  print(new DownCastCollection<Object>(list));
}

Now the class:

import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class DownCastCollection<E> extends AbstractCollection<E> implements Collection<E> {
private Collection<? extends E> delegate;

public DownCastCollection(Collection<? extends E> delegate) {
    super();
    this.delegate = delegate;
}

@Override
public int size() {
    return delegate ==null ? 0 : delegate.size();
}

@Override
public boolean isEmpty() {
    return delegate==null || delegate.isEmpty();
}

@Override
public boolean contains(Object o) {
    if(isEmpty()) return false;
    return delegate.contains(o);
}
private class MyIterator implements Iterator<E>{
    Iterator<? extends E> delegateIterator;

    protected MyIterator() {
        super();
        this.delegateIterator = delegate == null ? null :delegate.iterator();
    }

    @Override
    public boolean hasNext() {
        return delegateIterator != null && delegateIterator.hasNext();
    }

    @Override
    public  E next() {
        if(!hasNext()) throw new NoSuchElementException("The iterator is empty");
        return delegateIterator.next();
    }

    @Override
    public void remove() {
        delegateIterator.remove();

    }

}
@Override
public Iterator<E> iterator() {
    return new MyIterator();
}



@Override
public boolean add(E e) {
    throw new UnsupportedOperationException();
}

@Override
public boolean remove(Object o) {
    if(delegate == null) return false;
    return delegate.remove(o);
}

@Override
public boolean containsAll(Collection<?> c) {
    if(delegate==null) return false;
    return delegate.containsAll(c);
}

@Override
public boolean addAll(Collection<? extends E> c) {
    throw new UnsupportedOperationException();
}

@Override
public boolean removeAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.removeAll(c);
}

@Override
public boolean retainAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.retainAll(c);
}

@Override
public void clear() {
    if(delegate == null) return;
        delegate.clear();

}

}

风吹雨成花 2025-02-04 04:37:04

该问题已正确识别为与差异相关的,但细节不正确。纯粹的函数列表是协变量数据函数,这意味着如果类型SUB是Super的子类型,那么Sub的列表绝对是Super列表的子类型。

但是,列表的可变性不是这里的基本问题。总体上,问题是可变性。这个问题是众所周知的,被称为协方差问题,我认为Castagna首先确定它,它完全彻底地破坏了对象取向为一般范式。它基于Cardelli和Reynolds制定的先前确定的差异规则。

有点过分简化,让我们考虑将T型的对象B分配给T型t的对象A作为突变。这是没有普遍性的:一个可以写入a = f(a)的突变,其中f:t - &gt; T.当然,问题是,尽管功能在其代码域中是协变量的,但它们在域中是违反的,但是由于作业,域和代码域是相同的,因此分配是不变的!

概括得出的是,亚型无法突变。但是,由于对象取向突变是基本的,因此对象取向本质上是有缺陷的。

这是一个简单的示例:在纯粹的功能设置中,对称矩阵显然是矩阵,它是亚型,没问题。现在,让我们添加矩阵可以在坐标(x,y)处设置单个元素的功能,而规则没有其他元素更改。现在,对称矩阵不再是亚型,如果您更改(x,y),您也已更改(y,x)。功能操作是delta:sym-&gt;垫子,如果您更改对称矩阵的一个元素,则会将一般的非对称矩阵回到后面。因此,如果您在MAT中加入了“更改一个元素”方法,则SYM不是亚型。实际上..几乎可以肯定没有适当的亚型。

将所有这些简单地放在所有这些方面:如果您具有具有广泛突变器的一般数据类型,则可以确定任何适当的子类型都不能支持所有这些突变:如果可以的话,它将像超类型与“适当”亚型的规范相反。

Java防止可变的可变列表无法解决实际问题:为什么几十年前被抹黑时,您为什么使用面向对象的垃圾(如Java)?

无论如何,这里都有一个合理的讨论:

The issue has been correctly identified as related to variance but the details are not correct. A purely functional list is a covariant data functor, which means if a type Sub is a subtype of Super, then a list of Sub is definitely a subtype of a list of Super.

However mutability of a list is not the basic problem here. The problem is mutability in general. The problem is well known, and is called the Covariance Problem, it was first identified I think by Castagna, and it completely and utterly destroys object orientation as a general paradigm. It is based on previously established variance rules established by Cardelli and Reynolds.

Somewhat oversimplifying, lets consider assignment of an object B of type T to an object A of type T as a mutation. This is without loss of generality: a mutation of A can be written A = f (A) where f: T -> T. The problem, of course, is that whilst functions are covariant in their codomain, they're contravariant in their domain, but with assignments the domain and codomain are the same, so assignment is invariant!

It follows, generalising, that subtypes cannot be mutated. But with object orientation mutation is fundamental, hence object orientation is intrinsically flawed.

Here's a simple example: in a purely functional setting a symmetric matrix is clearly a matrix, it is a subtype, no problem. Now lets add to matrix the ability to set a single element at coordinates (x,y) with the rule no other element changes. Now symmetric matrix is no longer a subtype, if you change (x,y) you have also changed (y,x). The functional operation is delta: Sym -> Mat, if you change one element of a symmetric matrix you get a general non-symmetric matrix back. Therefore if you included a "change one element" method in Mat, Sym is not a subtype. In fact .. there are almost certainly NO proper subtypes.

To put all this in easier terms: if you have a general data type with a wide range of mutators which leverage its generality you can be certain any proper subtype cannot possibly support all those mutations: if it could, it would be just as general as the supertype, contrary to the specification of "proper" subtype.

The fact Java prevents subtyping mutable lists fails to address the real issue: why are you using object oriented rubbish like Java when it was discredited several decades ago??

In any case there's a reasonable discussion here:

https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)

愛放△進行李 2025-02-04 04:37:04

以javase 教程

public abstract class Shape {
    public abstract void draw(Canvas c);
}

public class Circle extends Shape {
    private int x, y, radius;
    public void draw(Canvas c) {
        ...
    }
}

public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas c) {
        ...
    }
}

让我们 (圈子)不应隐含地将动物清单视为(形状)是因为这种情况:

// drawAll method call
drawAll(circleList);


public void drawAll(List<Shape> shapes) {
   shapes.add(new Rectangle());    
}

因此,Java“ Architects”有2个解决此问题的选项:

  1. 不认为亚型是隐含的,并且给出了supertype,并给出了supertype,并给出了。编译错误,就像现在发生的那样,

  2. 将亚型考虑为supertype,并限制了“添加”。 “方法(因此,在绘制方法中,如果将传递圆形列表,则可以通过comply of Shape的列表,应检测到这一点,并以编译错误限制您这样做)。

出于明显的原因,选择了第一种方式。

Lets take the example from JavaSE tutorial

public abstract class Shape {
    public abstract void draw(Canvas c);
}

public class Circle extends Shape {
    private int x, y, radius;
    public void draw(Canvas c) {
        ...
    }
}

public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas c) {
        ...
    }
}

So why a list of dogs (circles) should not be considered implicitly a list of animals (shapes) is because of this situation:

// drawAll method call
drawAll(circleList);


public void drawAll(List<Shape> shapes) {
   shapes.add(new Rectangle());    
}

So Java "architects" had 2 options which address this problem:

  1. do not consider that a subtype is implicitly it's supertype, and give a compile error, like it happens now

  2. consider the subtype to be it's supertype and restrict at compile the "add" method (so in the drawAll method, if a list of circles, subtype of shape, would be passed, the compiler should detected that and restrict you with compile error into doing that).

For obvious reasons, that chose the first way.

小姐丶请自重 2025-02-04 04:37:04

我们还应该考虑编译器如何威胁通用类别:每当我们填写通用参数时,在“实例化”中。

因此,我们有 listofanimal listofdog list> listofcat 等,它们是不同的类,当我们指定时,编译器最终被编译器“创建”通用论点。这是一个平坦的层次结构(实际上关于 list 根本不是层次结构)。

在通用类的情况下,为什么协方差是没有意义的另一个论点是,在基本上,所有类都是相同的 - list 实例。通过填充通用参数不会扩展类,专门为 list ,它只是使其适用于特定的通用参数。

We should also take in consideration how the compiler threats the generic classes: in "instantiates" a different type whenever we fill the generic arguments.

Thus we have ListOfAnimal, ListOfDog, ListOfCat, etc, which are distinct classes that end up being "created" by the compiler when we specify the generic arguments. And this is a flat hierarchy (actually regarding to List is not a hierarchy at all).

Another argument why covariance doesn't make sense in case of generic classes is the fact that at base all classes are the same - are List instances. Specialising a List by filling the generic argument doesn't extend the class, it just makes it work for that particular generic argument.

晨光如昨 2025-02-04 04:37:04

这个问题已得到充分识别。但是有一个解决方案;使 dosomething 通用:

<T extends Animal> void doSomething<List<T> animals) {
}

现在您可以用list&lt; dog&gt;或列表&lt; cat&gt;或列表&lt;动物&gt;。

The problem has been well-identified. But there's a solution; make doSomething generic:

<T extends Animal> void doSomething<List<T> animals) {
}

now you can call doSomething with either List<Dog> or List<Cat> or List<Animal>.

古镇旧梦 2025-02-04 04:37:04

另一个解决方案是建立一个新列表

List<Dog> dogs = new ArrayList<Dog>(); 
List<Animal> animals = new ArrayList<Animal>(dogs);
animals.add(new Cat());

another solution is to build a new list

List<Dog> dogs = new ArrayList<Dog>(); 
List<Animal> animals = new ArrayList<Animal>(dogs);
animals.add(new Cat());
故事与诗 2025-02-04 04:37:04

除了乔恩·斯基特(Jon Skeet)的答案外,使用此示例代码:

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

在最深的层面上,这里的问题是动物共享参考。这意味着一种制作这项工作的一种方法是复制整个列表,这将破坏参考平等:

// This code is fine
List<Dog> dogs = new ArrayList<Dog>();
dogs.add(new Dog());
List<Animal> animals = new ArrayList<>(dogs); // Copy list
animals.add(new Cat());
Dog dog = dogs.get(0);   // This is fine now, because it does not return the Cat

调用 list&lt; andial&gt;动物= new arrayList&lt;&gt;(狗); ,随后您无法将动物> 直接分配给 dogs cats :

// These are both illegal
dogs = animals;
cats = animals;

因此 :因此您不能将错误的亚型 Animal 放入列表中,因为没有错误的子类型 - subtype 的任何对象?扩展动物可以添加到动物中。

显然,这改变了语义,因为列表动物不再共享,因此添加到一个列表中不会添加到另一个列表中(这正是您想要的,为了避免可以将 CAT 添加到仅包含 dog 对象的列表中)。此外,复制整个列表效率可能会降低。但是,这确实通过打破参考平等解决了类型的等效问题。

Further to the answer by Jon Skeet, which uses this example code:

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

At the deepest level, the problem here is that dogs and animals share a reference. That means that one way to make this work would be to copy the entire list, which would break reference equality:

// This code is fine
List<Dog> dogs = new ArrayList<Dog>();
dogs.add(new Dog());
List<Animal> animals = new ArrayList<>(dogs); // Copy list
animals.add(new Cat());
Dog dog = dogs.get(0);   // This is fine now, because it does not return the Cat

After calling List<Animal> animals = new ArrayList<>(dogs);, you cannot subsequently directly assign animals to either dogs or cats:

// These are both illegal
dogs = animals;
cats = animals;

therefore you can't put the wrong subtype of Animal into the list, because there is no wrong subtype -- any object of subtype ? extends Animal can be added to animals.

Obviously, this changes the semantics, since the lists animals and dogs are no longer shared, so adding to one list does not add to the other (which is exactly what you want, to avoid the problem that a Cat could be added to a list that is only supposed to contain Dog objects). Also, copying the entire list can be inefficient. However, this does solve the type equivalence problem, by breaking reference equality.

ゃ懵逼小萝莉 2025-02-04 04:37:04

我看到这个问题已经多次回答,只想在同一问题上提出我的意见。

让我们继续创建一个简化的动物类层次结构。

abstract class Animal {
    void eat() {
        System.out.println("animal eating");
    }
}

class Dog extends Animal {
    void bark() { }
}

class Cat extends Animal {
    void meow() { }
}

现在,让我们看看我们的老朋友数组,我们知道支持多态性的支持 -

class TestAnimals {
    public static void main(String[] args) {
        Animal[] animals = {new Dog(), new Cat(), new Dog()};
        Dog[] dogs = {new Dog(), new Dog(), new Dog()};
        takeAnimals(animals);
        takeAnimals(dogs);
    }

    public void takeAnimals(Animal[] animals) {
        for(Animal a : animals) {
            System.out.println(a.eat());
        }
    }   
}

类良好,当我们运行上面的类时,我们

animal eating
animal eating
animal eating
animal eating
animal eating
animal eating

在此处指出的是,在这里注意到takeanimals()方法已定义为采用任何类型动物的东西,都可能需要一系列类型的动物,也可能需要一系列狗,因为狗是动物。因此,这是行动中的多态性。

现在让我们与仿制药一起使用这种相同的方法,

现在说我们对代码进行了一些调整,然后使用阵列而不是数组 -

class TestAnimals {
    public static void main(String[] args) {
        ArrayList<Animal> animals = new ArrayList<Animal>();
        animals.add(new Dog());
        animals.add(new Cat());
        animals.add(new Dog());
        takeAnimals(animals);
    }

    public void takeAnimals(ArrayList<Animal> animals) {
        for(Animal a : animals) {
            System.out.println(a.eat());
        }
    }   
}

上面的类将编译并产生输出 -

animal eating
animal eating
animal eating
animal eating
animal eating
animal eating

因此我们知道这是有效的,现在让我们对此类别进行调整。钻头多态使用动物类型 -

class TestAnimals {
    public static void main(String[] args) {
        ArrayList<Animal> animals = new ArrayList<Animal>();
        animals.add(new Dog());
        animals.add(new Cat());
        animals.add(new Dog());

        ArrayList<Dog> dogs = new ArrayList<Dog>();
        takeAnimals(animals);
        takeAnimals(dogs);
    }

    public void takeAnimals(ArrayList<Animal> animals) {
        for(Animal a : animals) {
            System.out.println(a.eat());
        }
    }   
}

看起来上面的类别是没有问题,因为TakeAnimals()方法旨在将任何类型的动物和狗IS-iS-a-a-a-Animal列出来,因此不应是交易中断器这里。

但是,不幸的是,编译器会丢下错误,并且不允许我们将狗阵列列表传递给可变的动物阵列列表。

你问为什么?

只是想象一下,如果Java允许狗阵列 - 狗 - 放入动物阵列 - 动物 - 然后在takeanimals()方法中,就会有人做一些事情 -

animals.add(new Cat());

认为这应该是可行的,因为理想情况下它是动物ArrayList,您应该可以将任何猫添加为Cat-is-s-A-A-Animal,但实际上您将狗类型的arraylist添加到了它。

因此,现在您必须认为阵列也应该发生同样的事情。您是对的。

如果有人试图使用数组进行相同的操作,那么数组也将引发错误,但是数组在运行时处理此错误,而ArrayLists在编译时处理此错误。

I see that the question has already been answered a number of times, just want to put in my inputs on the same question.

Lets us go ahead and create a simplified Animal class hierarchy.

abstract class Animal {
    void eat() {
        System.out.println("animal eating");
    }
}

class Dog extends Animal {
    void bark() { }
}

class Cat extends Animal {
    void meow() { }
}

Now let us have a look at our old friend Arrays, which we know support polymorphism implicitly-

class TestAnimals {
    public static void main(String[] args) {
        Animal[] animals = {new Dog(), new Cat(), new Dog()};
        Dog[] dogs = {new Dog(), new Dog(), new Dog()};
        takeAnimals(animals);
        takeAnimals(dogs);
    }

    public void takeAnimals(Animal[] animals) {
        for(Animal a : animals) {
            System.out.println(a.eat());
        }
    }   
}

The class compiles fine and when we run the above class we get the output

animal eating
animal eating
animal eating
animal eating
animal eating
animal eating

The point to note here is that the takeAnimals() method is defined to take anything which is of type Animal, it can take an array of type Animal and it can take an array of Dog as well because Dog-is-a-Animal. So this is Polymorphism in action.

Let us now use this same approach with generics,

Now say we tweak our code a little bit and use ArrayLists instead of Arrays -

class TestAnimals {
    public static void main(String[] args) {
        ArrayList<Animal> animals = new ArrayList<Animal>();
        animals.add(new Dog());
        animals.add(new Cat());
        animals.add(new Dog());
        takeAnimals(animals);
    }

    public void takeAnimals(ArrayList<Animal> animals) {
        for(Animal a : animals) {
            System.out.println(a.eat());
        }
    }   
}

The class above will compile and will produce the output -

animal eating
animal eating
animal eating
animal eating
animal eating
animal eating

So we know this works, now lets tweak this class a little bit to use Animal type polymorphically -

class TestAnimals {
    public static void main(String[] args) {
        ArrayList<Animal> animals = new ArrayList<Animal>();
        animals.add(new Dog());
        animals.add(new Cat());
        animals.add(new Dog());

        ArrayList<Dog> dogs = new ArrayList<Dog>();
        takeAnimals(animals);
        takeAnimals(dogs);
    }

    public void takeAnimals(ArrayList<Animal> animals) {
        for(Animal a : animals) {
            System.out.println(a.eat());
        }
    }   
}

Looks like there should be no problem in compiling the above class as the takeAnimals() method is designed to take any ArrayList of type Animal and Dog-is-a-Animal so it should not be a deal breaker here.

But, unfortunately the compiler throws an error and doesn't allow us to pass a Dog ArrayList to a variable expecting Animal ArrayList.

You ask why?

Because just imagine, if JAVA were to allow the Dog ArrayList - dogs - to be put into the Animal ArrayList - animals - and then inside the takeAnimals() method somebody does something like -

animals.add(new Cat());

thinking that this should be doable because ideally it is an Animal ArrayList and you should be in a position to add any cat to it as Cat-is-also-a-Animal, but in real you passed a Dog type ArrayList to it.

So, now you must be thinking the the same should have happened with the Arrays as well. You are right in thinking so.

If somebody tries to do the same thing with Arrays then Arrays are also going to throw an error but Arrays handle this error at runtime whereas ArrayLists handle this error at compile time.

好菇凉咱不稀罕他 2025-02-04 04:37:03

no,a list&lt; dog&gt; is a list&lt; andial&gt; 。考虑使用 list&lt; Animal&gt; - 您可以添加 任何动物...包括猫。现在,您可以从逻辑上添加猫吗?绝对不是。

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

突然,您有一个非常困惑的猫。

现在,您不能 cat 添加到 list&lt;?扩展动物&gt; ,因为您不知道它是 list&lt; cat&gt; 。您可以检索一个值,并知道它将是动物,但是您不能添加任意动物。对于 list&lt;?超级动物&gt; - 在这种情况下,您可以安全地向其添加 Animal ,但是您对此一无所知,因为它可以是>列表&lt; object&gt;

No, a List<Dog> is not a List<Animal>. Consider what you can do with a List<Animal> - you can add any animal to it... including a cat. Now, can you logically add a cat to a litter of puppies? Absolutely not.

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

Suddenly you have a very confused cat.

Now, you can't add a Cat to a List<? extends Animal> because you don't know it's a List<Cat>. You can retrieve a value and know that it will be an Animal, but you can't add arbitrary animals. The reverse is true for List<? super Animal> - in that case you can add an Animal to it safely, but you don't know anything about what might be retrieved from it, because it could be a List<Object>.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文