簡體   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