[英]Java Generics Argument Type mismatch going from class generic to method generic
[英]Bound mismatch error on generics type argument for a generic class that extends another generic class
我對 java 中的 generics 和 inheritance 有疑問。
我想從可以從給定類型創建實例的“createManager”方法中實例化一個“ObjectManager”class。 這個“ObjectManager”有一些實用方法(例如:“getDescription”)。
此方法允許我從一個類型實例化一個“ObjectManager”,然后獲取例如它的描述(此處為 hash)。
如果輸入類型可分配給類型“Foo”,我還想為“FooManager”專門化“ObjectManager”,以便我可以覆蓋“getDescription”方法。 因此,我在“createManager”方法中測試輸入的類型,以了解我是創建“ObjectManager”還是“FooManager”。
該代碼有效,但我不知道對應於“new FooManager(type)”的 output 的通用類型。 在我看來,對於 Eclipse 快速修復,類型應該是 <T> 但這會導致錯誤:
綁定不匹配:類型 T 不是 FooManager<T> 類型的有界參數 <T extends Foo> 的有效替代品
我的代碼:
import java.lang.reflect.InvocationTargetException;
import java.util.Objects;
public class GenericBug {
private GenericBug() {}
public static void main(String[] args) {
ObjectManager<Foo> objectManager1 = createManager(Foo.class);
System.out.println(objectManager1.getDescription());
ObjectManager<Object> objectManager2 = createManager(Object.class);
System.out.println(objectManager2.getDescription());
}
private static <T> ObjectManager<T> createManager(Class<T> type) {
if(Foo.class.isAssignableFrom(type))
return new FooManager<T>(type); //Error: Bound mismatch
return new ObjectManager<T>(type);
}
}
class ObjectManager<T>{
public T val;
public ObjectManager(Class<T> type) {
try {
val = type.getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
}
public String getDescription() {
return "Hash: " + Objects.hash(val);
}
}
class FooManager<T extends Foo> extends ObjectManager<T>{
public FooManager(Class<T> type) {
super(type);
}
@Override
public String getDescription() {
return super.getDescription() + " value: " + val.getVal();
}
}
class Foo{
private double val = Math.random();
public Foo() {}
public double getVal() {
return val;
}
}
所以我的問題是:為什么會出現這個錯誤? 如何在沒有警告的情況下修復它? 而且,作為獎勵,為什么 Eclipse 提供了無法編譯的解決方案? 這是 Eclipse 錯誤嗎?
預先感謝您的回答。
問題是您正在混合 generics 的編譯時安全性和通過isAssignableFrom
進行的運行時檢查。
我不知道更好的方法:
private static <T> ObjectManager<T> createFooManager(Class<T> type) {
if (Foo.class.isAssignableFrom(type)) {
return (ObjectManager<T>) getIt((Class<? extends Foo>) type);
}
return new ObjectManager<>(type);
}
private static <R extends Foo> ObjectManager<R> getIt(Class<R> cls) {
return new FooManager<>(cls);
}
不過,這會引發unchecked warning
。
發生該錯誤是因為您沒有編譯時保證 T 是 Foo 的實例。 您只有運行時保證 Foo 可以從 T 分配,編譯器並不關心。 您可以通過抑制它們來完全編譯而不會發出警告:
@SuppressWarnings({ "unchecked", "rawtypes" })
private static <T> ObjectManager<T> createFooManager(Class<T> type) {
if(Foo.class.isAssignableFrom(type))
return new FooManager(type); //Error: Bound mismatch
return new ObjectManager<T>(type);
}
這可能不是您正在尋找的答案,但我認為您無法在不壓制警告的情況下擺脫警告。 我從這些警告的必然性中推斷出最初的方法是不正確的。 為什么需要傳入 class 作為參數? 如果 class 參數在編譯時已知,您可以簡單地為不同的類調用不同的方法。 如果 class 在編譯時未知,則您永遠不應該在 position 中從該通用參數中受益。
“ ……為什么會出現這個錯誤? ……”
javac
在您的原始代碼上報告的錯誤為您提供了以下原因:
...GenericBug.java:14: error: type argument T#1 is not within bounds of type-variable T#2
return new FooManager<T>(type); //Error: Bound mismatch
^
where T#1,T#2 are type-variables:
T#1 extends Object declared in method <T#1>createFooManager(Class<T#1>)
T#2 extends Foo declared in class FooManager
1 error
T#1 is not within bounds of type-variable T#2
因為:
<T> ObjectManager<T> createFooManager(...)
意味着您將T
(又名T#1
)定義為沒有界限。FooManager<T extends Foo>
的類型參數T
(又名T#2
)是用邊界extends Foo
定義的。 並且通用方法的T
不會像通用 class 要求的那樣擴展Foo
。 所以它越界了。“……怎么解決……”
您原來的Foo.class.isAssignableFrom(type)
方法讓我覺得不是很面向對象。 所以基本上,我已經以這種方式修復了它。
...
private static <T> ObjectManager<T> createObjectManager(Class<T> type) {
return new ObjectManager<>(type);
}
private static <S extends Foo> ObjectManager<S> createFooManager(Class<S> type) {
return new FooManager<>(type);
}
...
您的 IRL 用例是什么並不明顯。 所以我不得不做出一些假設。 就像我假設您很可能將其與Foo
的更專業實現一起使用。 所以我介紹了一個FooJr
來演示解決方案如何處理這個問題:
ObjectManager<? extends Foo> objectManager1 = createFooManager(Foo.class);
System.out.println(objectManager1.getDescription());
ObjectManager<?> objectManager2 = createObjectManager(Object.class);
System.out.println(objectManager2.getDescription());
objectManager1 = createFooManager(FooJr.class);
System.out.println(objectManager1.getDescription());
objectManager2 = createObjectManager(FooJr.class);
System.out.println(objectManager2.getDescription());
說一種解決方案比另一種解決方案“更好”或“更差”是主觀的; 也許是個人品味的問題。 但是可以客觀地說(雙關語),一種解決方案可以比另一種解決方案更面向對象。 有些解決方案肯定比其他解決方案更安全。
“……沒有警告? ……”
這個解決方案被證明是更安全的。 使用-Xlint:unchecked
運行它。 編譯器不會報告任何未經檢查的警告。 這不需要任何@SuppressWarnings(...)
。
„......為什么 Eclipse 提供了無法編譯的解決方案? ……”
Eclipse 只是向您展示它認為解決問題的最佳方法。 IDE 的本質是給一些東西一個 PASS, javac
或 JLS 絕對不允許。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.