简体   繁体   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 {}

The code compiles. 代码会编译。 But when I do this: 但是当我这样做时:

    PairJ<Car> twoCars = new PairJ(new Car(), new Car());
    Tank actuallyACar = twoCars.replaceFirst(new Tank()).second();

It still compiles but gives a cast exception when running, since the second element in the pair is a car, not a tank. 它仍然可以编译,但在运行时会给出强制转换异常,因为该对中的第二个元素是汽车,而不是坦克。

So I changed it to this: 所以我将其更改为:

public <R, T extends R> Pair<R> replaceFirst(R newFirst) {
    return new Pair(newFirst, second());
}

But this code still compiles and gives the same exception: 但是此代码仍然可以编译并给出相同的异常:

    PairJ<Car> twoCars = new PairJ(new Car(), new Car());
    Tank actuallyACar = twoCars.replaceFirst(new Tank()).second();

Seems like the declaration of T extends R is not working here. 似乎T的声明扩展R在这里不起作用。

How can I enforce type safety here? 如何在此处强制执行类型安全?

More specifically, how can I make sure that java infers 更具体地说,我如何确保Java推断

twoCars.replaceFirst(new Tank())

to return a Pair of Vehicle instead of a Pair of tanks? 返回一对车辆而不是一对坦克? So I would get compile time error when trying to assign its second element to a variable of the type Tank? 因此,当尝试将其第二个元素分配给Tank类型的变量时,我会得到编译时错误? And to reduce the chances of having run time exceptions? 并减少发生运行时异常的机会?

Edit: 编辑:

In Scala, we can do it like this: 在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] makes sure that R is a base type of T, how do we do the same in Java? [R>:T]确保R是T的基本类型,我们如何在Java中做同样的事情?

how can I make sure that java infers 我如何确保Java推断

 twoCars.replaceFirst(new Tank()) 

to return a Pair of Vehicle instead of a Pair of tanks? 返回一对车辆而不是一对坦克?

Provide a type argument explicitly 明确提供类型参数

twoCars.<Vehicle>replaceFirst(new Tank())

You'll get a compiler error when trying to invoke 尝试调用时会出现编译器错误

Tank actuallyACar = twoCars.<Vehicle>replaceFirst(new Tank()).second();

First, pay attention to this form: 首先,请注意以下形式:

PairJ<Car> twoCars = new PairJ(new Car(), new Car());

You're getting a compiler warning in which you're doing an unchecked assignment from PairJ to PairJ<Car> . 您将收到一个编译器警告,其中您正在执行从PairJPairJ<Car>的未经检查的分配。 You may run into gotchas with this later. 您稍后可能会遇到麻烦。

You can avoid this by providing the generic type, or letting Java (7.0+) infer it for you with the diamond operator. 您可以通过提供泛型类型来避免这种情况,或者让Java(7.0+)通过菱形运算符为您推断出它。

PairJ<Car> twoCars = new PairJ<Car>(new Car(), new Car());
// or
PairJ<Car> twoCars = new PairJ<>(new Car(), new Car());

The first problem is here: 第一个问题在这里:

public <R> PairJ<R> replaceFirst(R newFirst) {
    return new PairJ(newFirst, second());
}

You're getting another unchecked assignment here, but this one is worse - you're actively changing the type of one of your parameters to something completely different. 您在这里得到了另一个未经检查的赋值,但这种情况更糟 -您正在将一个参数的类型主动更改为完全不同的参数。

The reason that this is not possible is due to the way your generic type is bound to your class. 之所以无法做到这一点,是因为您的泛型类型绑定到您的类的方式。 You explicitly state that, in your constructor, you expect two arguments bound to some generic type T . 您明确声明,在构造函数中,您需要两个绑定到某个泛型T What you're doing here is binding only one to R . 您在这里所做的只是将一个绑定到R

If you were to "fix" the issue by doing type inference as described above, you'd get a compilation failure (which is good - you want these to fail at compile time as opposed to run time). 如果您要通过如上所述的类型推断来“解决”问题,则会导致编译失败(这很好-您希望它们在编译时而不是运行时失败)。

public <R> PairJ<R> replaceFirst(R newFirst) {
    return new PairJ<R>(newFirst, second());
}

The above fails due to it requiring parameters bound to R, R , not R, T . 由于需要参数绑定到R, R而不是R, T ,上述操作失败。

The second problem is here: 第二个问题在这里:

class Vehicle {}

class Car extends Vehicle {}

class Tank extends Vehicle {}

Your hierarchy mandates that, while a Car is a Vehicle , and a Tank is a Vehicle , a Car is not a Tank . 您的层次结构要求,虽然CarVehicle ,而TankVehicleCar不是Tank So the ClassCastException is appropriate; 因此, ClassCastException是适当的; you really can't cast a Car to a Tank . 你真的无法Car投向Tank

This would fail since they're not convertible between the two. 这将失败,因为它们在两者之间不可转换。

Tank actuallyACar = twoCars.replaceFirst(new Tank()).second();

There's a solution here; 这里有一个解决方案。 if you want to keep this API, then you'll need two type parameters for your class. 如果要保留此API,则您的类将需要两个类型参数。 This means that you can now pass in a new type R freely when calling replaceFirst . 这意味着您现在可以在调用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());
    }
}

The fun part now comes in calling the method. 现在,有趣的部分在于调用方法。 We can construct it pretty much the same way we did before, now with two Car type parameters. 我们现在可以使用两个Car类型参数来构造它,几乎可以像以前一样构造它。

PairJ<Car, Car> twoCars = new PairJ<>(new Car(), new Car());

As I said earlier, a Tank isn't a Car , so this just ain't gonna work: 正如我之前说的, Tank不是Car ,所以这是行不通的:

Tank actuallyACar = twoCars.replaceFirst(new Tank()).second();

...but this will : ...但这

Car actuallyACar = twoCars.replaceFirst(new Tank()).second();

The above more boiled down to the way you had your class hierarchy laid out. 以上内容归结为您对类层次结构进行布局的方式。 It would have never worked in Java, even if you got the generics on the class right, to cast from a Tank to a Car . 即使您在类上正确使用了泛型,也无法将Java从Tank转换为Car ,这在Java中永远不会起作用。 While they share a common ancestor, one is not the other. 虽然他们有一个共同的祖先,但一个不是另一个。

(Modern example: you can't cast a String to an Integer , even though they're both children of Object .) (现代示例:即使它们都是Object子代,也不能将String转换为Integer 。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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