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&gt;:T]确保R是T的基类型,我们如何在Java中做同样的事情?
答案 0 :(得分:1)
如何确保java推断
twoCars.replaceFirst(new Tank())
返回一对车而不是一对坦克?
明确提供类型参数
twoCars.<Vehicle>replaceFirst(new Tank())
尝试调用
时,您将收到编译器错误Tank actuallyACar = twoCars.<Vehicle>replaceFirst(new Tank()).second();
答案 1 :(得分:0)
首先,请注意这种形式:
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());
}
你在这里得到另一个未选中的作业,但是这个更糟 - 你正在积极地将你的一个参数的类型改为完全不同。< / p>
这是不可能的原因是由于您的泛型类型绑定到您的类的方式。您明确声明,在构造函数中,您期望两个参数绑定到某个泛型类型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
, a Car
不是Tank
。所以ClassCastException
是合适的;你真的无法将Car
投放到Tank
。
这会失败,因为它们不能在两者之间转换。
Tank actuallyACar = twoCars.replaceFirst(new Tank()).second();
这里有一个解决方案;如果你想保留这个API,那么你的类需要两个类型参数。这意味着您现在可以在调用R
时自由传递新类型replaceFirst
。
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();
以上更多归结为您的类层次结构的布局方式。即使您在课堂上使用了泛型,从Tank
转换为Car
,它也不会在Java中工作。虽然他们有共同的祖先,但其中一个不是另一个。
(现代例子:你不能将String
投射到Integer
,即使他们都是Object
的孩子。)