![](/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.