![](/img/trans.png)
[英]Java Generics: Use generic class as type parameter of another generic class
[英]Java generic: how to declare a generic parameter as the base type of another generic parameter?
public class PairJ<T> {
private T first;
private T second;
public PairJ(T first, T second) {
this.first = first;
this.second = second;
}
public T first() {
return this.first;
}
public T second() {
return this.second;
}
public <R> Pair<R> replaceFirst(R newFirst) {
return new Pair(newFirst, second());
}
}
class Vehicle {}
class Car extends Vehicle {}
class Tank extends Vehicle {}
代码会编译。 但是当我这样做时:
PairJ<Car> twoCars = new PairJ(new Car(), new Car());
Tank actuallyACar = twoCars.replaceFirst(new Tank()).second();
它仍然可以编译,但在运行时会给出强制转换异常,因为该对中的第二个元素是汽车,而不是坦克。
所以我将其更改为:
public <R, T extends R> Pair<R> replaceFirst(R newFirst) {
return new Pair(newFirst, second());
}
但是此代码仍然可以编译并给出相同的异常:
PairJ<Car> twoCars = new PairJ(new Car(), new Car());
Tank actuallyACar = twoCars.replaceFirst(new Tank()).second();
似乎T的声明扩展R在这里不起作用。
如何在此处强制执行类型安全?
更具体地说,我如何确保Java推断
twoCars.replaceFirst(new Tank())
返回一对车辆而不是一对坦克? 因此,当尝试将其第二个元素分配给Tank类型的变量时,我会得到编译时错误? 并减少发生运行时异常的机会?
编辑:
在Scala中,我们可以这样做:
class Pair[T](val first: T, val second: T) {
def replaceFirst[R >: T](newFirst: R): Pair[R] = new Pair[R](newFirst, second)
}
[R>:T]确保R是T的基本类型,我们如何在Java中做同样的事情?
我如何确保Java推断
twoCars.replaceFirst(new Tank())
返回一对车辆而不是一对坦克?
明确提供类型参数
twoCars.<Vehicle>replaceFirst(new Tank())
尝试调用时会出现编译器错误
Tank actuallyACar = twoCars.<Vehicle>replaceFirst(new Tank()).second();
首先,请注意以下形式:
PairJ<Car> twoCars = new PairJ(new Car(), new Car());
您将收到一个编译器警告,其中您正在执行从PairJ
到PairJ<Car>
的未经检查的分配。 您稍后可能会遇到麻烦。
您可以通过提供泛型类型来避免这种情况,或者让Java(7.0+)通过菱形运算符为您推断出它。
PairJ<Car> twoCars = new PairJ<Car>(new Car(), new Car());
// or
PairJ<Car> twoCars = new PairJ<>(new Car(), new Car());
第一个问题在这里:
public <R> PairJ<R> replaceFirst(R newFirst) {
return new PairJ(newFirst, second());
}
您在这里得到了另一个未经检查的赋值,但这种情况更糟 -您正在将一个参数的类型主动更改为完全不同的参数。
之所以无法做到这一点,是因为您的泛型类型绑定到您的类的方式。 您明确声明,在构造函数中,您需要两个绑定到某个泛型T
。 您在这里所做的只是将一个绑定到R
如果您要通过如上所述的类型推断来“解决”问题,则会导致编译失败(这很好-您希望它们在编译时而不是运行时失败)。
public <R> PairJ<R> replaceFirst(R newFirst) {
return new PairJ<R>(newFirst, second());
}
由于需要参数绑定到R, R
而不是R, T
,上述操作失败。
第二个问题在这里:
class Vehicle {}
class Car extends Vehicle {}
class Tank extends Vehicle {}
您的层次结构要求,虽然Car
是Vehicle
,而Tank
是Vehicle
, 但Car
不是Tank
。 因此, ClassCastException
是适当的; 你真的无法将Car
投向Tank
。
这将失败,因为它们在两者之间不可转换。
Tank actuallyACar = twoCars.replaceFirst(new Tank()).second();
这里有一个解决方案。 如果要保留此API,则您的类将需要两个类型参数。 这意味着您现在可以在调用replaceFirst
时自由传递新的R
类型。
class PairJ<S, T> {
private S first;
private T second;
// accessors modified
public <R> PairJ<R, T> replaceFirst(R newFirst) {
return new PairJ<>(newFirst, second());
}
}
现在,有趣的部分在于调用方法。 我们现在可以使用两个Car
类型参数来构造它,几乎可以像以前一样构造它。
PairJ<Car, Car> twoCars = new PairJ<>(new Car(), new Car());
正如我之前说的, Tank
不是Car
,所以这是行不通的:
Tank actuallyACar = twoCars.replaceFirst(new Tank()).second();
...但这会 :
Car actuallyACar = twoCars.replaceFirst(new Tank()).second();
以上内容归结为您对类层次结构进行布局的方式。 即使您在类上正确使用了泛型,也无法将Java从Tank
转换为Car
,这在Java中永远不会起作用。 虽然他们有一个共同的祖先,但一个不是另一个。
(现代示例:即使它们都是Object
子代,也不能将String
转换为Integer
。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.