简体   繁体   English

是列表<dog>列表的子类<animal> ? 为什么 Java generics 不是隐式多态的?</animal></dog>

[英]Is List<Dog> a subclass of List<Animal>? Why are Java generics not implicitly polymorphic?

I'm a bit confused about how Java generics handle inheritance / polymorphism.我对 Java generics 如何处理 inheritance / 多态性有点困惑。

Assume the following hierarchy -假设以下层次结构 -

Animal (Parent)动物(父母)

Dog - Cat (Children)-(儿童)

So suppose I have a method doSomething(List<Animal> animals) .所以假设我有一个方法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.根据 inheritance 和多态性的所有规则,我假设List<Dog>List<Animal>并且List<Cat>List<Animal> - 因此任何一个都可以传递给此方法。 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) .如果我想实现这种行为,我必须通过说doSomething(List<? extends Animal> animals)明确告诉该方法接受 Animal 的任何子类的列表。

I understand that this is Java's behavior.我明白这是 Java 的行为。 My question is why ?我的问题是为什么 Why is polymorphism generally implicit, but when it comes to generics it must be specified?为什么多态一般是隐式的,但是到了generics就必须指定?

No, a List<Dog> is not a List<Animal> .不, List<Dog>不是List<Animal> Consider what you can do with a List<Animal> - you can add any animal to it... including a cat.考虑一下您可以用List<Animal>做什么——您可以向其中添加任何动物……包括猫。 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>现在,您不能Cat添加到List<? extends Animal> List<? extends Animal> because you don't know it's a List<Cat> . List<? extends Animal>因为你不知道它是一个List<Cat> You can retrieve a value and know that it will be an Animal , but you can't add arbitrary animals.您可以检索一个值并知道它将是一个Animal ,但您不能添加任意动物。 The reverse is true for List<? super Animal> List<? super Animal> 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> . List<? super Animal> - 在这种情况下,您可以安全地向其添加一个Animal ,但您不知道可以从中检索到什么,因为它可能是一个List<Object>

What you are looking for is called covariant type parameters .您正在寻找的是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> ).这意味着如果 object 的一种类型可以在方法中替换为另一种类型(例如, Animal可以替换为Dog ),这同样适用于使用这些对象的表达式(因此List<Animal>可以替换为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> .假设你有一个List<Dog> ,它被用作List<Animal> What happens when you try to add a Cat to this List<Animal> which is really a List<Dog> ?当您尝试将 Cat 添加到这个实际上是List<Dog>List<Animal>时会发生什么? 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添加语法以允许将类型参数指定为协变会很有用,这样可以避免? extends Foo ? extends Foo in method declarations, but that does add additional complexity.在方法声明中? extends Foo ,但这确实增加了额外的复杂性。

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; List<Dog>不是List<Animal>的原因是,例如,您可以将Cat插入List<Animal> ,但不能插入List<Dog> ...您可以使用通配符来制作generics 在可能的情况下更具可扩展性; for example, reading from a List<Dog> is the similar to reading from a List<Animal> -- but not writing.例如,从List<Dog>读取类似于从List<Animal>读取——但不是写入。

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. Java 语言中的 GenericsJava 教程中关于 Generics的部分对 generics 为什么有些东西是或不是多态或允许的有很好、深入的解释。

A point I think should be added to what other answers mention is that while我认为应该在其他答案中提到的一点是,虽然

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

it is also true that这也是事实

A list of dogs is-a list of animals in English (under a reasonable interpretation) A list of dogs is-英文动物名录(合理解释下)

The way the OP's intuition works - which is completely valid of course - is the latter sentence. OP 直觉的工作方式——当然是完全有效的——是后一句话。 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.然而,如果我们应用这种直觉,我们会得到一种在其类型系统中不是 Java 风格的语言:假设我们的语言确实允许将一只猫添加到我们的狗列表中。 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".换句话说:Java中的A List<Dog>在英文中并不是“a list of dogs”的意思,它的意思是“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.更一般地说, OP 的直觉适用于一种语言,在这种语言中,对对象的操作可以更改其类型,或者更确切地说,对象的类型是其值的(动态)function。

I would say the whole point of Generics is that it doesn't allow that.我会说 Generics 的全部意义在于它不允许这样做。 Consider the situation with arrays, which do allow that type of covariance:考虑 arrays 的情况,它确实允许这种类型的协方差:

  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).该代码编译正常,但会引发运行时错误(第二行中的java.lang.ArrayStoreException: java.lang.Boolean )。 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. Generics 的要点是添加编译时类型安全,否则你可以坚持使用没有 generics 的普通 class。

Now there are times where you need to be more flexible and that is what the ? super Class现在有些时候您需要更加灵活,这就是? super Class ? super Class and ? extends Class ? super Class? extends Class ? extends Class are for. ? extends Class是为了。 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.前者是当您需要插入类型Collection (例如)时,后者是当您需要以类型安全的方式从中读取时。 But the only way to do both at the same time is to have a specific type.但同时进行这两项操作的唯一方法是拥有一个特定的类型。

To understand the problem it's useful to make comparison to arrays.为了理解这个问题,与 arrays 进行比较是很有用的。

List<Dog> is not subclass of List<Animal> . List<Dog>不是List<Animal>的子类。
But Dog[] is subclass of Animal[] .但是Dog[]Animal[]的子类。

Arrays are reifiable and covariant . Arrays是具体化和协变的。
Reifiable means their type information is fully available at runtime. Reifiable意味着它们的类型信息在运行时是完全可用的。
Therefore arrays provide runtime type safety but not compile-time type safety.因此 arrays 提供运行时类型安全但不提供编译时类型安全。

    // 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 反之亦然:
Generics are erased and invariant . Generics 被擦除且不变
Therefore generics can't provide runtime type safety, but they provide compile-time type safety.因此 generics 不能提供运行时类型安全,但它们提供编译时类型安全。
In the code below if generics were covariant it will be possible to make heap pollution at line 3.在下面的代码中,如果 generics 是协变的,则可能会在第 3 行造成堆污染

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

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 Consumer s and Supplier s for Animal s.但是您只能为Animal传递ConsumerSupplier If you have a Mammal consumer, but a Duck supplier, they should not fit although both are animals.如果您有一个Mammal消费者,但是一个Duck供应商,尽管它们都是动物,但它们不应该适合。 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.确保我们只能使用为消费者提供正确类型 object 的供应商。

OTOH, we could as well do OTOH,我们也可以这样做

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 .我们 go 另一种方式:我们定义Supplier的类型并限制它可以放入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.其中,具有Life -> Animal -> Mammal -> Dog , Cat等直观关系,我们甚至可以将Mammal放入Life消费者中,但不能将String放入Life消费者中。

The basis logic for such behavior is that Generics follow a mechanism of type erasure.这种行为的基本逻辑是Generics遵循类型擦除机制。 So at run time you have no way if identifying the type of collection unlike arrays where there is no such erasure process.因此,在运行时,您无法识别不像arrays那样没有此类擦除过程的collection类型。 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.现在,如果 java 允许调用者将 Animal 类型的列表添加到此方法中,那么您可能会将错误的东西添加到集合中,并且在运行时它也会由于类型擦除而运行。 While in case of arrays you will get a run time exception for such scenarios...而在 arrays 的情况下,您将获得此类场景的运行时异常......

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....现在我相信存在类型擦除,以便在没有 generics 的情况下与遗留 java 兼容....

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:所以,从Java版本10开始解决这个问题如下:

(Note: S = superclass) (注意:S = 超类)

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.如果绝对安全,这个 function 将执行强制转换,如果强制转换不安全,则执行复制。

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如需深入解释(考虑到此处其他答案提到的潜在陷阱),请参阅相关问题和我 2022 年的回答: https://stackoverflow.com/a/72195980/773113

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> .即使 class DogAnimal的子类型,参数化类型List<Dog>也不是List<Animal>的子类型。 In contrast, covariant subtyping is used by arrays, so the array type Dog[] is a subtype of Animal[] .相反,arrays 使用协变子类型,因此数组类型Dog[]Animal[]的子类型。

Invariant subtyping ensures that the type constraints enforced by Java are not violated.不变子类型确保不违反 Java 强制执行的类型约束。 Consider the following code given by @Jon Skeet:考虑@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.正如@Jon Skeet 所述,此代码是非法的,因为否则它会在狗预期时返回猫,从而违反类型约束。

It is instructive to compare the above to analogous code for arrays.将上面的代码与 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.数组在运行时以这种方式携带其类型 JVM 可以强制协变子类型化的类型安全。

To understand this further let's look at the bytecode generated by javap of the class below:为了进一步理解这一点,让我们看看下面 class 的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");
    }
}

Using the command javap -c Demonstration , this shows the following Java bytecode:使用命令javap -c Demonstration ,这显示了以下 Java 字节码:

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.这使得参数化类型只不过是语法糖。

Actually you can use an interface to achieve what you want.实际上你可以使用一个接口来实现你想要的。

public interface Animal {
    String getName();
    String getVoice();
}
public class Dog implements Animal{
    @Override 
    String getName(){return "Dog";}
    @Override
    String getVoice(){return "woof!";}

} }

you can then use the collections using然后您可以使用集合使用

List <Animal> animalGroup = new ArrayList<Animal>();
animalGroup.add(new Dog());

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.当你想在构造函数中传递列表或迭代它时,这很有用。

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.需要注意的一件事是,对于 Collections(列表、集合等),主要问题是添加到集合中。 That is where things break down.这就是事情崩溃的地方。 Even removing is OK.即使删除也可以。

In most cases, we can use Collection<? extends T>在大多数情况下,我们可以使用Collection<? extends T> Collection<? extends T> rather then Collection<T> and that should be the first choice. Collection<? extends T>而不是Collection<T> ,这应该是首选。 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>我在这里展示一个 class DownCastCollection,它可以转换一个Collection<? extends T> 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. Collection<? extends T>Collection<T> (我们可以为 List、Set、NavigableSet 等定义类似的类),在使用标准方法时非常不方便。 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.下面是一个如何使用它的示例(在这种情况下我们也可以使用Collection<? extends Object> ,但我尽量简单地说明如何使用 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:现在是 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();

}

} }

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.纯函数列表是协变数据函子,这意味着如果类型 Sub 是 Super 的子类型,则 Sub 列表肯定是 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.这个问题众所周知,被称为协方差问题,我认为它首先是由 Castagna 发现的,它彻底彻底地破坏了 object 作为一般范式的方向。 It is based on previously established variance rules established by Cardelli and Reynolds.它基于先前由 Cardelli 和 Reynolds 建立的方差规则。

Somewhat oversimplifying, lets consider assignment of an object B of type T to an object A of type T as a mutation.有点过分简化,让我们考虑将类型 T 的 object B 分配给类型 T 的 object A 作为突变。 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!这不失一般性:A 的一个突变可以写成 A = f (A) 其中 f: T -> T。当然,问题是虽然函数在它们的余域中是协变的,但它们在它们的域中是逆变的域,但是对于赋值,域和辅域是相同的,所以赋值是不变的!

It follows, generalising, that subtypes cannot be mutated.概括地说,子类型不能突变。 But with object orientation mutation is fundamental, hence object orientation is intrinsically flawed.但是 object 方向突变是基本的,因此 object 方向本质上是有缺陷的。

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.现在让我们向矩阵添加在坐标 (x,y) 处设置单个元素的功能,并遵循其他元素不变的规则。 Now symmetric matrix is no longer a subtype, if you change (x,y) you have also changed (y,x).现在对称矩阵不再是子类型,如果你改变 (x,y) 你也改变了 (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.函数运算是 delta:Sym -> Mat,如果你改变一个对称矩阵的一个元素,你会得到一个一般的非对称矩阵。 Therefore if you included a "change one element" method in Mat, Sym is not a subtype.因此,如果您在 Mat 中包含“更改一个元素”方法,则 Sym 不是子类型。 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?? Java 阻止子类型化可变列表的事实未能解决真正的问题:为什么你使用面向 object 的垃圾,比如 Java 几十年前它就被抹黑了?

In any case there's a reasonable discussion here:无论如何,这里有一个合理的讨论:

https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) https://en.wikipedia.org/wiki/Covariance_and_contravariance_(计算机科学)

Lets take the example from JavaSE tutorial让我们以 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) {
        ...
    }
}

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:所以 Java“建筑师”有 2 个选项可以解决这个问题:

  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).将子类型视为它的超类型并在编译“添加”方法时进行限制(因此在 drawAll 方法中,如果将传递圆列表、形状的子类型,编译器应该检测到并限制你执行编译错误那)。

For obvious reasons, that chose the first way.出于显而易见的原因,它选择了第一种方式。

We should also take in consideration how the compiler threats the generic classes: in "instantiates" a different type whenever we fill the generic arguments.我们还应该考虑编译器如何威胁泛型类:每当我们填充泛型 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).因此,我们有ListOfAnimalListOfDogListOfCat等,它们是不同的类,当我们指定泛型 arguments 时,它们最终由编译器“创建”。这是一个平面层次结构(实际上关于List根本不是层次结构) .

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.协变在泛型类的情况下没有意义的另一个论点是所有类在基础上都是相同的——都是List实例。 Specialising a List by filling the generic argument doesn't extend the class, it just makes it work for that particular generic argument.通过填充通用参数来专门化List不会扩展 class,它只是让它适用于该特定的通用参数。

The problem has been well-identified.问题已经很清楚了。 But there's a solution;但是有一个解决方案; make doSomething generic: make doSomething通用的:

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

now you can call doSomething with either List<Dog> or List<Cat> or List<Animal>.现在您可以使用 List<Dog> 或 List<Cat> 或 List<Animal> 调用 doSomething。

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());

Further to the answer by Jon Skeet, which uses this example code:进一步 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?

At the deepest level, the problem here is that dogs and animals share a reference.在最深层次上,这里的问题是dogsanimals共享一个参考。 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);调用List<Animal> animals = new ArrayList<>(dogs); , you cannot subsequently directly assign animals to either dogs or cats : ,您不能随后直接将animals分配dogscats

// 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因此,您不能将错误的Animal子类型放入列表中,因为没有错误的子类型——任何 object 子类型? extends Animal ? extends Animal can be added to animals . ? extends Animal可以添加到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).显然,这改变了语义,因为列表animalsdogs不再共享,所以添加到一个列表不会添加到另一个列表(这正是你想要的,以避免将Cat添加到列表中的问题应该只包含Dog对象)。 Also, copying the entire list can be inefficient.此外,复制整个列表可能效率低下。 However, this does solve the type equivalence problem, by breaking reference equality.但是,这确实通过打破引用相等性解决了类型等价问题。

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.让我们 go 向前创建一个简化的 Animal class 层次结构。

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-现在让我们看看我们的老朋友 Arrays,我们知道它隐式支持多态性——

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 class 编译正常,当我们运行上面的 class 时,我们得到 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.这里要注意的一点是,takeAnimals() 方法被定义为接受 Animal 类型的任何东西,它可以接受 Animal 类型的数组,也可以接受 Dog 的数组,因为 Dog-is-a-Animal。 So this is Polymorphism in action.所以这就是多态性在起作用。

Let us now use this same approach with generics,现在让我们对 generics 使用同样的方法,

Now say we tweak our code a little bit and use ArrayLists instead of Arrays -现在假设我们稍微调整一下代码并使用 ArrayLists 而不是 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 -上面的 class 将编译并生成 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 以多态地使用 Animal 类型 -

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.看起来编译上面的 class 应该没有问题,因为 takeAnimals() 方法被设计为接受 Animal 和 Dog-is-a-Animal 类型的任何 ArrayList,所以它在这里不应该是一个交易破坏者。

But, unfortunately the compiler throws an error and doesn't allow us to pass a Dog ArrayList to a variable expecting Animal ArrayList.但是,不幸的是,编译器抛出一个错误,不允许我们将 Dog ArrayList 传递给需要 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 -因为试想一下,如果 JAVA 允许 Dog ArrayList - dogs - 被放入 Animal ArrayList - animals - 然后在 takeAnimals() 方法中有人做了类似的事情 -

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.认为这应该是可行的,因为理想情况下它是一个动物 ArrayList,你应该在 position 中添加任何猫作为猫也是动物,但实际上你传递了一个狗类型 ArrayList 给它。

So, now you must be thinking the the same should have happened with the Arrays as well.所以,现在你一定在想同样的事情也应该发生在 Arrays 上。 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.如果有人试图用 Arrays 做同样的事情,那么 Arrays 也会抛出一个错误,但是 Arrays 在运行时处理这个错误,而 ArrayLists 在编译时处理这个错误。

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

相关问题 狗是动物,但清单 <Dog> 没有列出 <Animal> 。 如何在泛型/多态函数中安全使用它? - Dog is Animal but list<Dog> is not list<Animal>. How to use it safely in a generic/polymorphic function? 将List <Animal>转换为List <Dog> - Casting List<Animal> to List<Dog> 泛型:列表<? extends Animal>与列表相同<Animal> ? - Generics : List<? extends Animal> is same as List<Animal>? java对象,我用动物speedy = new dog()创建动物或狗吗? 为什么? - java objects, do I create an animal or a dog with animal speedy = new dog(); and why? 是否可以在没有演员的情况下从 Animal 列表中获取 Dog 列表? - Is it possible to get a list of Dog from a list of Animal without a cast? 是否可以使用类型参数 List 覆盖继承的方法<animal>与列表<dog> ?</dog></animal> - Is it possible to override an inherited method with type parameter List<Animal> with List<Dog>? Java Generics — 将子类列表分配给超类列表 - Java Generics — Assigning a list of subclass to a list of superclass 在Java泛型中创建子类元素的列表 - Create list of subclass elements in java generics 为什么不能列出<!--? extends Animal-->替换为列表<animal> ?</animal> - Why can't List<? extends Animal> be replaced with List<Animal>? Java泛型:使用子类的列表设置超类的列表 - Java Generics: set List of superclass using List of subclass
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM