簡體   English   中英

避免返回通配符類型

[英]Avoiding Returning Wildcard Types

我有一個帶有通配符類型集合的類,它是一個單例,例如:

public ObliviousClass{

    private static final ObliviousClass INSTANCE = new ObliviousClass();

    private Map<Key, Type<?>> map = new HashMap<Key, Type<?>>();

    public void putType(Key key, Type<?> type){
        map.put(type);
    }

    // returns the singleton
    public static ObliviousClass getInstance(){
        return INSTANCE;
    }

}

我希望能夠在客戶端代碼中向這個集合添加不同的參數化類型:

void clientMethod(){
    ObliviousClass oc = ObliviousClass.getInstance();

    Type<Integer> intType = ...
    Type<String> stringType = ...

    oc.putType(new Key(0), intType);
    oc.putType(new Key(1), stringType);
}

到目前為止,據我所知,一切正常。 但是客戶端還需要能夠獲得Type<?>提供的Key 因此,將向ObliviousClass添加類似於以下內容的方法:

public Type<?> getType(Key key){
    return map.get(key);
}

但在我手邊的Effective Java 中,我讀到:

不要使用通配符類型作為返回類型。

我理解這個問題,因為客戶端必須轉換返回的Type<?> 但我真的不想讓ObliviousClass成為泛型類型ObliviousClass<T> ,因為那樣我上面的客戶端代碼將無法工作......

我正在嘗試做的事情有更好的設計嗎? -我目前的解決方案是為客戶端提供靜態方法; 類似的東西:

public static <T> void getType(ObliviousClass instance, Key key, Type<T> dest){
    dest = (Type<T>)instance.getType(key);
}

我四處尋找,但找不到完全消除我困惑的答案。

這是在映射中存儲給定類型的多個實例的類型安全方法。 關鍵是您需要在檢索值時提供一個Class實例以執行運行時類型檢查,因為靜態類型信息已被刪除。

class ObliviousClass {

  private final Map<Key, Object> map = new HashMap<Key, Object>();

  public Object put(Key key, Object value)
  {
    return map.put(key, value);
  }

  public <T> T get(Key key, Class<? extends T> type)
  {
    return type.cast(map.get(key)); 
  }

}

用法如下所示:

oc.put(k1, 42);
oc.put(k2, "Hello!");
...
Integer i = oc.get(k1, Integer.class);
String s = oc.get(k2, String.class);
Integer x = oc.get(k2, Integer.class); /* Throws ClassCastException */

只需輸入您的課程:

public ObliviousClass <T> {

    private Map<Key, Type<T>> map = new HashMap<Key, Type<T>>();

    public void putType(Key key, Type<T> type){
        map.put(type);
    }

    public Type<T> getType(Key key){
        map.get(key);
    }
}

僅供參考,此時您已經使用了委托模式

您的示例客戶端代碼需要聲明ObliviousClass兩個實例: ObliviousClass<String>ObliviousClass<Integer>

編輯:

如果您必須擁有混合類型的包,您可以在您的方法上強加一個類型,但您將收到一個編譯器警告,提示不安全的強制轉換:

public class ObliviousClass {

    private final Map<Key, Type<?>> map = new HashMap<Key, Type<?>>();

    public void putType(Key key, Type<?> value) {
       map.put(key, value);
    }

    @SuppressWarnings("unchecked")
    public <T> Type<T> getType1(Key key, Class<T> typeClass) {
       return (Type<T>)map.get(key); 
    }

    @SuppressWarnings("unchecked")
    public <T> Type<T> getType2(Key key) {
        return (Type<T>) map.get(key);
    }
}

客戶端可以像這樣鍵入對這些方法的調用:

Type<Integer> x = obliviousClass.getType1(key, Integer.class);
Type<Integer> y = obliviousClass.<Integer>getType2(key);

選擇您喜歡的那個並使用它。

對於多年后遇到這個問題的人來說,這不是 Java 泛型的設計方式。 (我打算發表評論,但有更多細節。)

通用圖案管理每個類型ID,而不是多個不同的類的單個父類。 如果我們考慮更簡單的 List<T>,一個字符串或整數列表(如 List<String> 或 List<Integer>)就是泛型的定義方式。 每類一班。 這樣,引用值時就有了一致的類型。 存儲不相關的類型與 List<Object> 相同。 只有程序員才能知道何時存儲了多種類型以及如何通過強制轉換來檢索它們。

將子類存儲到父類是可以的,但是當從集合中訪問而不進行強制轉換時,父類的聯系是全部已知的。 例如,使用 Map<String, Runnable> 之類的接口定義的通用集合。 但是,即使將其他公共方法添加到實現中,也只有 run() 方法是可見的(除非程序員顯式地強制轉換)。 要訪問其他方法,必須進行強制轉換。

這是 Java 中的一個限制。 可以定義一種語言來了解 L 值類型——甚至是 Java。 但事實並非如此。 添加新功能時,[Sun 和] Oracle 會考慮許多向后兼容的考慮。 使用泛型編譯的代碼旨在在具有類型擦除功能的舊 JVM 上運行。 一旦確定泛型是一致引用,Java 就會在編譯時使用類型擦除。 字節碼使用 Object 就好像實例被(有點)定義為 List。 如果選擇放棄向后兼容性,例如 Java 9 和 11,那么多種類型可能是可行的。

按照設計,您的ObliviousClass不知道它所持有的項目的參數化類型。 所以為了類型安全,你應該避免這樣的設計:-\\

但如果你想保留它,首先你將不得不鑄造。 沒有辦法解決這個問題。 但是你這樣做的方式很容易出錯。 例如:

oc.put(k1, intType);
oc.put(k2, strType);
Type<Integer> tint = oc.get(k1, Integer.class)
Type<String>  tstr = oc.get(k1, String.class)  // typo in k2: compile fine

最糟糕的是,由於類型擦除,只有在您實際使用tstr時它才會在運行時失敗,而不是從ObliviousClass獲取它時。

因此,您可以通過以其他方式跟蹤參數化類型來提高安全性。 例如,您可以將鍵與類型相關聯,而不是丟失它:

@Value // lombok
class Key<T> {
    private int index;
}

class Type<T> {}

class ObliviousClass {

    // side note: static final can be public safely
    public static final ObliviousClass instance = new ObliviousClass();

    private List<Type<?>> map = new ArrayList<>();

    public <T> Key<T> appendType(Type<T> type){
        // here, I found it nicer that obliviousClass generates and return the key
        // otherwise use:  "public <T> void appendType(key<T> key, Type<T> type)"
        // that binds parametrized type of both key and type arguments
        map.add(type);
        return new Key<>(map.size() - 1);
    }


    public <T> Type<T> get(Key<T> key){
        return (Type<T>) map.get(key.index);
    }
}

然后你可以使用它,例如:

    Type<Integer> intType = new Type<>();
    Type<String>  strType = new Type<>();
    Key<Integer> k1 = ObliviousClass.instance.appendType(intType);
    Key<String>  k2 = ObliviousClass.instance.appendType(strType);

    Type<Integer> t1 = ObliviousClass.instance.get(k1);
    Type<String>  t2 = ObliviousClass.instance.get(k2);
    Type<String>  t3 = ObliviousClass.instance.get(k1); // won't compile

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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