为什么无法将从另一个泛型类继承的类作为泛型类传递?

huangapple go评论60阅读模式
英文:

Why cannot pass the class extended from another generic class as generic class?

问题

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

private final ObjectPool<ProcessorMechanicsRoom> processorsPool;
... new ProcessorMechanicsRoom(processorsPool);
public class ProcessorMechanicsRoom
        extends ProcessorMechanics<ProcessMechanicsRoom, IMechanicsRoom, IMechanicsRoomCallback> {
    ...
    public ProcessorMechanicsRoom(ObjectPool<ProcessorMechanicsRoom> pool) {
    }
    super(pool); // 问题出在这里
}
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,M,C>> pool) {...}
    ...
}

问题在于无法将 ObjectPool<ProcessorMechanicsRoom> 传递给超级构造函数(代码2)。所以我感到困惑。

英文:

I develop some big project. Some parts of my code:

1)

private final ObjectPool<ProcessorMechanicsRoom> processorsPool;
... new ProcessorMechanicsRoom(processorsPool);

2)

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

3)

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,M,C>> pool) {...}
    ...
}

The problem is that ObjectPool<ProcessorMechanicsRoom> cannot be passed into super-constructor (code 2). So i am confused.

答案1

得分: 2

有一种叫做"方差"的概念。

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

java.lang.Integer 继承自 java.lang.Number 继承自 java.lang.Object

协变性

在一个协变的系统中,你可以写成:

Number x = new Integer();

但你不能写成:

Integer y = new Number();

你可能会猜想,Java 中的基本赋值操作等都是协变的。但这不是唯一的方式。

逆变性

在一个逆变的系统中,你不能写成

Number x = new Integer();

但反过来,这实际上是有效的:

Integer y = new Number();

不变性

这是一种不灵活的情况;在这种情况下,两者都无法使用。你唯一能做的是:

Integer y = new Integer();

好的,那么,泛型呢?

虽然对于基本的内容,Java 是协变的,但泛型不是。泛型可以是逆变的、协变的或不变的,这取决于你如何编写泛型。

  • 协变:List<? extends Number> list = new ArrayList<Integer>(); // 合法
  • 逆变:List<? super Integer> list = new ArrayList<Number>(); // 合法
  • 不变:List<Integer> list = new ArrayList<Integer>(); // 只能使用 Integer

你选择了不变。因此,只有 ProcessorMechanics 可以;你的 ProcessorMechanicsRoom 是一个子类,因此除非你的类型关系允许协变,否则你不能这样做,而它并不允许。将其改为 ? extends 就可以工作。

嗯,什么鬼?为什么?

因为... 生活。现实生活就是这样。

想象一下如果不是这样。那么,我可以这样做,从而破坏一切:

List<Integer> ints = new ArrayList<Integer>();
List<Number> numbers = ints; // 标记这一行!
numbers.add(new Double(5.0));
Integer x = ints.get(0); // 错误!

在上面的代码中,如果它能编译并运行,最后一行会出错,因为.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 中无法表达这一点。

对于列表来说很容易理解:"逆变性 = 添加操作".add().addAll() 等),"协变性 = 读操作""不变性 = 既能添加又能读"。对于其他带有泛型的类型,情况可能不太简单。

假设你的 ProcessorMechanics 类只会读取,那么你可以将它定义为协变的,写成:

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

There's this thing called variance.

Let's use some types we are all familiar with:

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

Covariance

In a covariant system, you can write:

Number x = new Integer();

but you cannot write:

Integer y = new Number();

As you might surmise, basic assignment and such in java is all covariant. But that's not the only way to do it.

Contravariance

In a contravariant system, you cannot write:

Number x = new Integer();

but on the flipside, this actually works:

Integer y = new Number();

Invariance

This is the inflexible one; in this one, neither works. The only thing you can do is:

Integer y = new Integer();

Okay, so, what about generics?

Whereas java is covariant for basic stuff, generics isn't. Generics is contravariant, or covariant, or invariant, depending on how you write the generics.

  • Covariant: List&lt;? extends Number&gt; list = new ArrayList&lt;Integer&gt;(); // legal
  • Contravariant: List&lt;? super Integer&gt; list = new ArrayList&lt;Number&gt;(); // legal
  • Invariant: List&lt;Integer&gt; list = new ArrayList&lt;Integer&gt;(); // only integer will do here

You've picked invariant. So only ProcessorMechanics will do; your ProcessorMechanicsRoom is a subclass, and therefore you can't do that unless your type relationship allows covariance, and it does not. Make that ? extends and it'll work.

Um, wtf? Why???

Because... life. That is how real life works.

Imagine it did not. I can do this, then, and break everything:

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

In the above, if it had compiled and run, the last line would be an error, as the .get(0) call would retrieve a double value which isn't an integer. Fortunately, the above does not compile; the error occurs on the marked line. That's.. because the compiler should disallow this. Generics by its very nature are invariant.

Now, covariance can exist. For example, if you have a method that will sum up the result of invoking .intValue() on each of the Numbers inside, then you could write:

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

but that's a bad way to write it; you've decreed that the parameter is invariant, thus, you cannot pass a List&lt;Integer&gt; to this thing. But the code is covariant. It would work just as well if you pass a list of integers. So, you should write that as public int sumAll(List&lt;? extends Number&gt; numbers) instead.

Here is an example of invariance:

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

Because we're adding a number here, you couldn't write List&lt;? extends Number&gt;. After all, we're adding an int and you can't do that to a List&lt;Double&gt;. The only acceptable lists you can feed in here are List&lt;Number&gt; and List&lt;Integer&gt; and there's no way to express that in java.

For lists, it's easy: "contravariance = adds" (.add(), .addAll(), etc), "covariance = reads", "invariance = does both". For other generified types it may not be that simple.

Presumably if your ProcessorMechanics class will only ever 'read', then you can make it covariant, and write:

public ProcessorMechanics(ObjectPool&lt;? extends ProcessorMechanics&lt;P, M, C&gt;&gt; pool) {...}

huangapple
  • 本文由 发表于 2020年8月26日 00:59:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/63583738.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定