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

我对Javagenerics如何处理inheritance/多态性有些困惑。

假设以下层次结构 –

动物 (家长)

(儿童)

所以假设我有一个方法doSomething(List<Animal> animals) 。 按照所有inheritance和多态的规则,我会假定一个List<Dog> 一个List<Animal> ,一个List<Cat> 一个List<Animal> – 所以任何一个都可以传递给这个方法。 不是这样。 如果我想实现这种行为,我必须明确地告诉方法通过说doSomething(List<? extends Animal> animals)接受动物子集的doSomething(List<? extends Animal> animals)

我明白这是Java的行为。 我的问题是为什么 ? 为什么多态一般是隐含的,但是当涉及到generics时,必须指定它?

不, List<Dog> 不是 List<Animal> 。 考虑你可以用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? 

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

现在,你不能添加一个CatList<? extends Animal> List<? extends Animal>因为你不知道它是一个List<Cat> 。 你可以检索一个值,并知道它是一个Animal ,但你不能添加任意动物。 对于List<? super Animal>是相反的 List<? super Animal> – 在这种情况下,你可以安全地添加一个Animal ,但是你不知道什么可能从它检索,因为它可能是一个List<Object>

你在找什么叫做协变types参数 。 问题是它们在一般情况下不是types安全的,特别是对于可变列表。 假设你有一个List<Dog> ,它可以作为一个List<Animal> 。 当你试图添加一个Cat到这个List<Animal>这真的是一个List<Dog>什么? 自动允许types参数是协变的,因此打破了types系统。

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

List<Dog>不是List<Animal>是,例如,您可以将Cat插入到List<Animal> ,但不能插入到List<Dog> …您可以使用通配符generics在可能的情况下更具可扩展性; 例如,从一个List<Dog>中读取类似于从List<Animal>读取 – 而不是写入。

Java语言中的generics和Javagenerics中的generics 部分对于为什么某些事物是多态的或者generics允许的有一个非常好的深入的解释。

generics的全部意义在于它不允许这样做。 考虑数组的情况,它允许这种types的协方差:

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

该代码编译好,但会引发运行时错误(第二行java.lang.ArrayStoreException: java.lang.Boolean )。 这不是types安全的。 generics的要点是增加编译时的types安全性,否则你可以坚持一个没有generics的普通类。

现在有些时候你需要更加灵活,那是什么? super Class ? super Class? extends Class ? extends Class是为了。 前者是当你需要插入到一个typesCollection (例如),而后者是当你需要从中读取,types安全的方式。 但同时做这两件事的唯一方法是有一个特定的types。

我认为应该加上其他 答案中提到的一点

在Java中 List<Dog>不是一个List<Animal>

这也是事实

狗的名单是一个英文的动物列表(以及合理的解释)

OP的直觉工作方式 – 当然是完全有效的 – 是后一句话。 但是,如果我们运用这个直觉,我们就会得到一个types系统中不是Java的语言:假设我们的语言允许在我们的狗列表中添加一个猫。 那是什么意思? 这意味着名单不再是狗的名单,而只是一个动物名单。 还有一张哺乳动物的名单,还有一张四angular形的名单。

换句话说,Java中的List<Dog>并不是英文中的“狗的列表”,而是指“可以有狗的列表,而不是其他的”。

更一般地说, OP的直觉适用于一种语言,在这种语言中,对象的操作可以改变它的types ,或者说,对象的types是它的价值的(dynamic的)function。

这种行为的基本逻辑是Generics遵循types擦除机制。 所以在运行时,如果要确定types的collection而不像arrays那样没有这样的擦除过程,那么你就没有办法。 所以回到你的问题…

所以假设有一个方法如下:

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

现在,如果Java允许调用者添加types的dynamic列表到这个方法,那么你可能会添加错误的东西到收集和运行时也会运行,因为types擦除。 而在数组的情况下,你会得到一个运行时exception这种情况下…

因此,本质上这个行为是被实现的,所以不能把错误的东西添加到集合中。 现在我相信types擦除存在,以便与没有generics的遗留Java兼容….

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

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

听起来不错,不是吗? 但你只能通过ConsumerSupplierAnimal 。 如果你有一个Mammal消费者,但一个Duck供应商,他们不应该适合,虽然都是动物。 为了禁止这一点,已经增加了额外的限制。

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

例如,

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

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

OTOH,我们也可以做

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

我们从另一个angular度去考虑:我们定义Supplier的types,并限制它可以放入Consumer

我们甚至可以做到

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

在那里, Life – > Animal – > Mammal – > DogCat等直观的关系,我们甚至可以把一个Mammal放入一个Life消费者,而不是一个Life消费者的String

其实你可以使用一个界面来实现你想要的。

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

}

你可以使用集合

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

如果您确定列表项是给定超types的子类,则可以使用以下方法来转换列表:

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

当您想要在构造函数中传递列表或迭代它时,这是有用的

要理解这个问题,比较数组是有用的。

List<Dog> 不是 List<Animal>子类。
但是 Dog[] Animal[]子类。

数组是可确定的和协变的
Reifiable意味着它们的types信息在运行时完全可用。
因此,数组提供了运行时types的安全性,但不是编译时types的安

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

仿制药反过来也是如此:
generics被擦除并且不变
因此generics不能提供运行时types的安全性,但它们提供编译时types的安全性。
在下面的代码中,如果generics是协变的,那么在第3行就可能造成堆污染 。

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

让我们以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“架构师”有两个选项可以解决这个问题:

  1. 不要认为子types是隐式的,它是超types的,给出一个编译错误,就像现在发生的那样

  2. 考虑子types是超types,并限制在编译“add”方法(所以在drawAll方法中,如果一个圆的列表,子types的forms将被传递,编译器应该检测到并限制你编译错误到做那)。

出于显而易见的原因,select了第一种方式。

答案以及其他答案是正确的。 我将添加一个解决scheme,我认为这将有所帮助。 我觉得这经常在编程中出现。 有一点需要注意的是,collections(列表,集合等)的主要问题是添加到collections。 这是事情发生的地方。 即使删除也行。

在大多数情况下,我们可以使用Collection<? extends T> Collection<? extends T>而不是Collection<T> ,那应该是第一select。 但是,我发现这样的情况并不容易。 这是否是最好的事情呢? 我在这里介绍一个类DownCastCollection可以转换一个Collection<? extends T> Collection<? extends T>为一个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)); } 

现在这个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(); } 

}