List<Dog> 是 List<Animal> 的子类吗?为什么 Java 泛型不是隐式多态的?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2745265/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 12:22:06  来源:igfitidea点击:

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

javagenericsinheritancepolymorphism

提问by froadie

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

我对 Java 泛型如何处理继承/多态有点困惑。

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>isa List<Animal>and a List<Cat>isa 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).

所以假设我有一个方法doSomething(List<Animal> animals)。根据继承和多态的所有规则,我会假设 a List<Dog>isaList<Animal>和 a List<Cat>isa List<Animal>- 所以任何一个都可以传递给这个方法。不是这样。如果我想实现这种行为,我必须通过说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?

我知道这是 Java 的行为。我的问题是为什么?为什么多态一般是隐式的,但涉及到泛型时必须指定?

采纳答案by Jon Skeet

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

不,List<Dog>不是一个List<Animal>。考虑一下你可以用 a 做什么List<Animal>- 你可以向它添加任何动物......包括一只猫。现在,你能合乎逻辑地在一窝小狗中添加一只猫吗?绝对不。

// 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 veryconfused cat.

突然间你有一只非常困惑的猫。

Now, you can'tadd a Catto 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 Animalto it safely, but you don't know anything about what might be retrieved from it, because it could be a List<Object>.

现在,您无法将 a 添加Cat到 aList<? extends Animal>因为您不知道它是 a List<Cat>。您可以检索一个值并知道它将是一个Animal,但您不能添加任意的动物。反之亦然List<? super Animal>- 在这种情况下,您可以Animal安全地向其添加,但您对可能从中检索到的内容一无所知,因为它可能是List<Object>.

回答by Michael Ekstrand

What you are looking for is called covariant typeparameters. This means that if one type of object can be substituted for another in a method (for instance, Animalcan 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.

您正在寻找的是所谓的协变类型参数。这意味着如果一种类型的对象可以在方法中替换为另一种类型(例如,Animal可以替换为Dog),这同样适用于使用这些对象的表达式(因此List<Animal>可以替换为List<Dog>)。问题是协方差对于可变列表一般来说是不安全的。假设您有一个List<Dog>,并且它被用作List<Animal>. 当您尝试将 Cat 添加到这List<Animal>实际上是一个 时会发生List<Dog>什么?自动允许类型参数协变打破了类型系统。

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

添加语法以允许将类型参数指定为协变会很有用,这避免了? extends Fooin 方法声明,但这确实增加了额外的复杂性。

回答by Michael Aaron Safyan

The reason a List<Dog>is not a List<Animal>, is that, for example, you can insert a Catinto 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.

aList<Dog>不是 a的原因是List<Animal>,例如,您可以将 a 插入Cat到 a 中List<Animal>,但不能插入到 a 中List<Dog>……您可以使用通配符使泛型在可能的情况下更具可扩展性;例如,从 a 读取List<Dog>类似于从 a 读取List<Animal>——但不是写入。

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

Java语言的泛型从Java教程仿制药科有一个很好的,深入的解释,为什么有些东西是或不是多晶型或泛型允许的。

回答by Yishai

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.Booleanin 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.

该代码编译良好,但引发运行时错误(java.lang.ArrayStoreException: java.lang.Boolean在第二行)。它不是类型安全的。泛型的重点是添加编译时类型安全,否则你可以坚持使用没有泛型的普通类。

Now there are times where you need to be more flexible and that is what the ? super Classand ? extends Classare 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.

现在,有时您需要更加灵活,这就是? super Class和 的? extends Class目的。前者是当你需要插入一个类型时Collection(例如),后者是你需要以类型安全的方式读取它的时候。但同时做这两个事情的唯一方法是拥有一个特定的类型。

回答by Hitesh

The basis logic for such behavior is that Genericsfollow a mechanism of type erasure. So at run time you have no way if identifying the type of collectionunlike arrayswhere there is no such erasure process. So coming back to your question...

这种行为的基本逻辑是Generics遵循类型擦除机制。所以在运行时,如果没有这样的擦除过程,你就无法识别collection不同的类型arrays。所以回到你的问题......

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...

现在,如果 java 允许调用者向此方法添加 Animal 类型的 List,那么您可能会在集合中添加错误的内容,并且在运行时它也会由于类型擦除而运行。而在数组的情况下,您将在此类情况下获得运行时异常...

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....

因此,本质上实现了这种行为,以便人们不能将错误的东西添加到集合中。现在我相信类型擦除的存在是为了在没有泛型的情况下与遗留 Java 兼容......

回答by einpoklum

A point I think should be added to what otheranswersmention is that while

我认为应该添加到其他答案提到的一点是,虽然

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

List<Dog>在 Java 中不是 -aList<Animal>

it is also true that

这也是事实

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

A list of dog is-a list of animal in English(嗯,在合理的解释下)

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.

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

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

换句话说:List<Dog>Java 中的A在英文中的意思不是“狗的列表”,它的意思是“一个可以有狗的列表,没有别的”。

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 的直觉适用于一种语言,在这种语言中,对对象的操作可以改变它们的类型,或者更确切地说,对象的类型是其值的(动态)函数。

回答by dan b

The answeras 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.

在大多数情况下,我们可以使用Collection<? extends T>而不是然后Collection<T>,这应该是首选。但是,我正在发现不容易做到这一点的情况。关于这是否总是最好的做法还有待商榷。我在这里展示了一个类 DownCastCollection,它可以将 a 转换Collection<? extends T>为 a Collection<T>(我们可以为 List、Set、NavigableSet 等定义类似的类),以便在使用标准方法非常不方便时使用。下面是一个如何使用它的示例(我们也可以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:

现在上课:

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

}

}

}

回答by glglgl

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 Mammalconsumer, but a Ducksupplier, they should not fit although both are animals. In order to disallow this, additional restrictions have been added.

听起来不错,不是吗?但是你只能为s传递Consumers 和s。如果您有消费者,但有供应商,则尽管两者都是动物,但它们不应该适合。为了禁止这种情况,增加了额外的限制。SupplierAnimalMammalDuck

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

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 Supplierand restrict that it can be put into the Consumer.

反过来说:我们定义了 的类型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, Catetc., we could even put a Mammalinto a Lifeconsumer, but not a Stringinto a Lifeconsumer.

其中,具有直观的关系Life- > Animal- > Mammal- > DogCat等等,我们甚至可以在一MammalLife消费者,而不是StringLife消费者。

回答by Angel Koh

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

回答by sagits

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 in a constructor or iterate over it

当您想在构造函数中传递列表或对其进行迭代时,这很有用