繁体   English   中英

Java泛型:如何将泛型参数声明为另一个泛型参数的基本类型?

[英]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());

您将收到一个编译器警告,其中您正在执行从PairJPairJ<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 {}

您的层次结构要求,虽然CarVehicle ,而TankVehicleCar不是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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM