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

假设以下层次结构-

动物(父母)

狗-猫(儿童)

所以假设我有一个doSomething方法(列出<Animal>动物)。根据继承和多态性的所有规则,我会假设List<Dog>是List<Animal>,List<Cat>是List<Animal>-因此任何一个都可以传递给这个方法。不是这样。如果我想实现这种行为,我必须通过说doSomething(list<?extendsAnimal>动物)来明确告诉方法接受Animal的任何子类的列表。

我知道这是Java的行为。我的问题是为什么?为什么多态性通常是隐式的,但当涉及泛型时,必须指定它?


当前回答

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

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

当您想要在构造函数内部传递列表或对其进行迭代时,这是非常有用的。

其他回答

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

不要认为子类型是隐式的,它是父类型,并给出编译错误,就像现在发生的那样将子类型视为它的父类型,并在编译“add”方法时进行限制(因此在drawAll方法中,如果要传递圆的列表(形状的子类型),编译器应该检测到这一点,并用编译错误限制您这样做)。

出于明显的原因,他们选择了第一条路。

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

List<Dog>不是List<Animal>的子类。但狗[]是动物[]的子类。

数组是可具体化和协变的。可重用意味着它们的类型信息在运行时完全可用。因此,数组提供运行时类型安全性,但不提供编译时类型安全。

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

对于泛型,情况也是如此:泛型被删除且不变。因此,泛型不能提供运行时类型安全,但它们提供编译时类型安全。在下面的代码中,如果泛型是协变的,则可能在第3行造成堆污染。

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

不,列表<狗>不是列表<动物>。考虑一下你可以用列表<动物>做什么——你可以在其中添加任何动物……包括一只猫。现在,你能在一窝小狗中加入一只猫吗?绝对不是。

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

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

现在,您不能将猫添加到列表<?扩展Animal>,因为你不知道它是List<Cat>。您可以检索一个值并知道它将是一个Animal,但不能添加任意的Animal。列表<?super Animal>-在这种情况下,您可以安全地将Animal添加到其中,但您不知道可能从中检索到什么,因为它可能是List<Object>。

子类型对于参数化类型是不变的。即使严格来说,类Dog是Animal的子类型,但参数化类型List<Dog>不是List<Animal>的子类型。相反,协变子类型由数组使用,因此数组类型狗[]是动物[]的一个亚型。

不变的子类型确保不违反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];

该代码是合法的。但是,引发数组存储异常。数组在运行时携带其类型,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字节码:

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
}

观察方法体的翻译代码是否相同。编译器通过删除来替换每个参数化类型。此属性至关重要,这意味着它不会破坏向后兼容性。

总之,参数化类型的运行时安全性是不可能的,因为编译器通过删除来替换每个参数化类型。这使得参数化类型只不过是语法糖。

答案和其他答案都是正确的。我将用我认为会有帮助的解决方案来补充这些答案。我认为这在编程中经常出现。需要注意的一点是,对于集合(列表、集合等),主要问题是添加到集合。这就是问题所在。即使移除也可以。

在大多数情况下,我们可以使用集合<?扩展T>而不是集合<T>,这应该是首选。然而,我发现这样做并不容易。关于这是否始终是最好的做法,这是一个值得争论的问题。我在这里介绍一个类DownCastCollection,它可以转换集合<?将T>扩展到集合<T>(我们可以为List、Set、NavigableSet等定义类似的类),以便在使用标准方法时使用。下面是一个如何使用它的示例(在这种情况下,我们也可以使用Collection<?extendsObject>,但我保持简单,以说明使用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();

}

}