【问题标题】:Why cannot pass the class extended from another generic class as generic class? [duplicate]为什么不能将从另一个泛型类扩展的类作为泛型类传递? [复制]
【发布时间】:2020-12-14 10:24:32
【问题描述】:

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

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

}

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

【问题讨论】:

  • 编辑您的问题并显示编译错误的全文。在设计说明中,同时使用 ProcessMechanics 和 ProcessorMechanics 作为类名,以及 ProcessMechanicsRoom 和 ProcessorMechanicsRoom,非常令人困惑。我可能会尝试在我自己的私人代码副本中替换这些名称,以澄清问题,但现在,很难将它们区分开来,我放弃了尝试。如果您的代码更易于理解,您将获得更多帮助。
  • 从逻辑上讲,ProcessorMechanicsRoom 可以转换为 ProcessorMechanics<...>。但我的 ide (intellij) 将其显示为错误。太合乎逻辑了!

标签: java oop generics extends generic-programming


【解决方案1】:

有一种东西叫做方差。

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

java.lang.Integer extends java.lang.Number extends 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&lt;? extends Number&gt; list = new ArrayList&lt;Integer&gt;(); // legal
  • 逆变:List&lt;? super Integer&gt; list = new ArrayList&lt;Number&gt;(); // legal
  • 不变量:List&lt;Integer&gt; list = new ArrayList&lt;Integer&gt;(); // 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) 调用将检索一个不是整数的双精度值。幸运的是,上面没有编译;错误发生在标记的行上。那是..因为编译器应该不允许这样做。泛型本质上是不变的

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

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

但这是一种不好的写法;您已经下令该参数是不变的,因此,您不能将 List&lt;Integer&gt; 传递给这个东西。但是代码是协变的。如果你传递一个整数列表,它也会起作用。所以,你应该写成public int sumAll(List&lt;? extends Number&gt; numbers)

这是一个不变性的例子:

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

因为我们在这里添加了一个数字,所以你不能写List&lt;? extends Number&gt;。毕竟,我们正在添加int,而您不能对List&lt;Double&gt; 这样做。您可以在此处输入的唯一可接受的列表是 List&lt;Number&gt;List&lt;Integer&gt;,并且无法在 java 中表达。

对于列表,这很简单:“逆变 = 添加”(.add().addAll() 等)、“协方差 = 读取”、“不变 = 两者都做”。对于其他泛型类型,可能就没那么简单了。

大概如果您的 ProcessorMechanics 类只会“读取”,那么您可以将其设为协变并写入:

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

【讨论】:

    猜你喜欢
    • 2011-08-09
    • 2011-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-14
    • 1970-01-01
    • 1970-01-01
    • 2019-05-24
    相关资源
    最近更新 更多