为什么不能将从另一个泛型类扩展的类作为泛型类传递?

问题描述

我开发了一个大项目。我的代码的某些部分:

1)

private final ObjectPool<ProcessorMechanicsRoom> processorsPool;
... new ProcessorMechanicsRoom(processorsPool);
public class ProcessorMechanicsRoom
        extends ProcessorMechanics<ProcessMechanicsRoom,IMechanicsRoom,IMechanicsRoomCallback> {
    ...
    public ProcessorMechanicsRoom(ObjectPool<ProcessorMechanicsRoom> pool) {
}
    super(pool); // the problem is here
}

public class ProcessorMechanics
        <P extends ProcessMechanics<M,C>,M extends IAMechanics<C>,C extends IAMechanicsCallback>
        extends Processor<P> {
    private final ObjectPool<ProcessorMechanics<P,M,C>> pool;
    ...
    public ProcessorMechanics(ObjectPool<ProcessorMechanics<P,C>> pool) {...}
    ...

}

问题是无法将ObjectPool 传递给超级构造函数代码2)。所以我很困惑。

解决方法

有一个叫做方差的东西。

让我们使用一些我们都熟悉的类型:

java.lang.Integer extends java.lang.Number extends java.lang.Object

协方差

在协变系统中,您可以编写:

Number x = new Integer();

但是您不能写:

Integer y = new Number();

您可能会猜到,java中的基本赋值都是协变的。但这不是唯一的方法。

Contravariance

在一个逆变系统中,您不能写

Number x = new Integer();

但在另一面,这实际上有效:

Integer y = new Number();

不变性

这是死板的;在这一过程中,两者均无效。您唯一可以做的是:

Integer y = new Integer();

好的,那么泛型呢?

尽管Java是基本变量的协变变量,但泛型不是。泛型是对变的,协变的或不变的,具体取决于您编写泛型的方式。

  • 协变量:List<? extends Number> list = new ArrayList<Integer>(); // legal
  • 变量:List<? super Integer> list = new ArrayList<Number>(); // legal
  • 不变式:List<Integer> list = new ArrayList<Integer>(); // only integer will do here

您选择了不变式。所以只有ProcessorMechanics会做;您的ProcessorMechanicsRoom是子类,因此除非您的类型关系允许协方差,否则您不能这样做。将其设置为? extends即可。

嗯,wtf?为什么?

因为...生活。这就是现实生活的方式。

想象一下没有。我可以这样做,然后破坏一切:

List<Integer> ints = new ArrayList<Integer>();
List<Number> numbers = ints; // MARK THIS LINE!
numbers.add(new Double(5.0));
Integer x = ints.get(0); // ERROR!

在上面,如果它已经编译并运行,则最后一行将是错误,因为.get(0)调用将检索不是整数的double值。幸运的是,以上内容并未编译;错误发生在标记的行上。那是..因为编译器不应该这样做。就其本质而言,泛型是不变的

现在,协方差可以存在。例如,如果您有一种方法可以汇总对每个内部数字调用.intValue()的结果,则可以编写:

public int sumAll(List<Number> list) {
   int result = 0;
   for (Number n : list) result += n.intValue();
   return result;
}

但这是一种糟糕的编写方式;您已经决定该参数是不变的,因此,您不能将List<Integer>传递给该对象。但是代码是协变的。如果您传递整数列表,它将同样有效。因此,您应该将其写为public int sumAll(List<? extends Number> numbers)

这是不变性的一个例子:

public void addSumToEnd(List<Number> list) {
    int sum = 0;
    for (Number n : list) sum += n.intValue();
    list.add(sum);
}

因为我们要在此处添加一个数字,所以您无法输入List<? extends Number>。毕竟,我们要添加int,而您不能在List<Double>上这样做。您可以在此处输入的唯一可接受的列表是List<Number>List<Integer>,无法用Java表示。

对于列表,很简单:“ contravariance =加”(.add().addAll()等),“ covariance =读取”,“ invariance =两者都做”。对于其他泛型类型,可能不是那么简单。

大概如果您的ProcessorMechanics类只会“读”,那么您可以使其协变并编写:

public ProcessorMechanics(ObjectPool<? extends ProcessorMechanics<P,M,C>> pool) {...}