为什么不能将另一个通用 class 扩展的 class 作为通用 class 传递?
Why cannot pass the class extended from another generic class as generic class?
我开发了一些大项目。我的部分代码:
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)。所以我很困惑。
有个东西叫做方差。
让我们使用一些我们都熟悉的类型:
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<? 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
是一个子 class,因此除非您的类型关系允许协变,否则您不能这样做,而事实并非如此。做那个 ? extends
就可以了。
嗯,卧槽?为什么???
因为……生活。现实生活就是这样。
想象一下吧。那么,我可以做到这一点,并打破一切:
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<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 class 只会 'read',那么你可以让它协变,然后写:
public ProcessorMechanics(ObjectPool<? extends ProcessorMechanics<P, M, C>> pool) {...}
我开发了一些大项目。我的部分代码:
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
有个东西叫做方差。
让我们使用一些我们都熟悉的类型:
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<? 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
是一个子 class,因此除非您的类型关系允许协变,否则您不能这样做,而事实并非如此。做那个 ? extends
就可以了。
嗯,卧槽?为什么???
因为……生活。现实生活就是这样。
想象一下吧。那么,我可以做到这一点,并打破一切:
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<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 class 只会 'read',那么你可以让它协变,然后写:
public ProcessorMechanics(ObjectPool<? extends ProcessorMechanics<P, M, C>> pool) {...}