簡體   English   中英

擴展另一個通用 class 的通用 class 的 generics 類型參數上的綁定不匹配錯誤

[英]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因為:

  1. <T> ObjectManager<T> createFooManager(...)意味着您將T又名T#1 )定義為沒有界限。
  2. 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM