2015-02-10 75 views
3

我有一個類將包含幾個不同對象的不同解析器實現。儘管我能夠在沒有任何警告的情況下存儲解析器實現,但從映射中獲取解析器會警告未經檢查的轉換異常。下面是一個簡化的摘錄:重新設計未經檢查的強制轉換警告

private Map<Class<?>, Parser<?>> parsers = new HashMap<>(); 

public <T> void addParser(Class<T> type, Parser<T> parser) { 
    parsers.put(type, parser); 
} 

private <T> Parser<T> parserFor(Class<T> type) { 
    // Compiler complains about unchecked cast below 
    return (Parser<T>) parsers.get(type); 
} 

是否有另一種方法來實現類似的邏輯,而不會導致未經檢查的轉換警告?

+0

沒有沒有真正的方法來做到這一點,沒有未經檢查的演員。只要你不做任何有趣的事情就沒關係。 – Radiodef 2015-02-10 04:27:39

回答

1

沒有辦法創建一個Map<Class<...>, Parser<...>>其中... -s可以是任何東西,但必須匹配一個鍵和它的值;所以你不可能讓編譯器爲你做檢查,其中檢索Class<T>保證給你一個Parser<T>。但是,您的代碼本身是正確的; 知道你的演員是正確的,即使編譯器沒有。

所以,當你知道你的演員是正確的,但Java不知道它,你能做什麼?

最好和最安全的方法是製作一段儘可能小的特定代碼,負責處理已檢查和未檢查邏輯之間的轉換,並確保未檢查的邏輯不會導致任何錯誤錯誤。然後,您只需使用適當的@SuppressWarnings註釋標記該代碼即可。例如,你可以有這樣的事情:

public abstract class Parser<T> { 
    private final Class<T> mType; 

    protected Parser(final Class<T> type) { 
     this.mType = type; 
    } 

    public final Class<T> getType() { 
     return mType; 
    } 

    @SuppressWarnings("unchecked") 
    public final <U> Parser<U> castToParserOf(final Class<U> type) { 
     if (type == mType) { 
      return (Parser<U>) this; 
     } else { 
      throw new ClassCastException("... useful message ..."); 
     } 
    } 
} 

這將允許您安全地寫在你的榜樣:

public <T> void addParser(final Parser<T> parser) { 
    parsers.put(parser.getType(), parser); 
} 

private <T> Parser<T> parserFor(final Class<T> type) { 
    return parsers.get(type).castToParserOf(type); 
} 
+0

出於好奇,如何抑制原始類型警告比抑制未經檢查的警告更好/更安全? – fayerth 2015-02-10 05:42:46

+1

@fayerth:不是的。兩者同樣優良 - 重要的部分是您只是儘可能地本地化非類型檢查的代碼,以便您對其正確性有信心。我的例子最後是'@SuppressWarnings(「raw」)',因爲你不能直接從'Parser '轉換成'Parser ' - 這不是編譯警告*,而是編譯錯誤*所以我需要一箇中間演員,而'(Parser)'是最簡單的演員。 (否則,我需要寫*兩個*演員,例如像'(Parser )(Parser )this'。) – ruakh 2015-02-10 05:46:05

+0

@fayerth:其實,對不起,更正:我剛剛在Eclipse中測試,並從'解析器'到'解析器'實際上工作正常。只是一個未經檢查的警告。所以我會改變我的答案。 – ruakh 2015-02-10 05:51:44

1

由於地圖parsers值類型是Parser<?>和你的方法的返回類型爲Parser<T>,這顯然給parsers.get(type)結果轉換爲T錯誤。

之一刪除編譯錯誤的方式是投類型Parser<T>

private <T> Parser<T> parserFor(Class<T> type) { 
    return (Parser<T>)parsers.get(type); 
} 

另外,還可以從你指定的解析器地圖爲Map<Class<?>, Parser<?>>返回類型更改爲Parser<?>。這也將清除編譯錯誤。

private <T> Parser<?> parserFor(Class<T> type) { 
    return parsers.get(type); 
} 

或者你可以添加類型參數到你的包裝類。

public class YourClass<T> { 
    private Map<Class<T>, Parser<T>> parsers = new HashMap<>(); 

    public void addParser(Class<T> type, Parser<T> parser) { 
    parsers.put(type, parser); 
    } 

    private Parser<T> parserFor(Class<T> type) { 
    return parsers.get(type); 
    } 
} 

我不確定哪一個可以正確應用,但是,儘量不要使用類型轉換。考慮我們爲什麼使用通用。

+1

在編譯錯誤上,這是我的一個錯字;我已經編輯了相應的問題。 對於你關於返回'Parser '的建議,我曾嘗試過,最初認爲它會起作用。不過,我後來意識到,當我使用'parserFor()'時,我得到了一個未經檢查的強制轉換警告,這基本上意味着警告現在只是在不同的地方。 理想情況下,類本身並不需要一個類型參數來完成它的工作,因爲它只是想得到一個特定類型的解析器並使用它。仍然會有某種方法可以避免這種警告嗎? – fayerth 2015-02-10 05:28:19

+0

返回一個通配符類型,這也是一個無界的是一個壞主意IMO。 'Parser '應該沒問題。 – 2015-02-10 19:32:16

1

考慮使用Google Guava的TypeToInstanceMap<Parser<?>>。這將讓你做這樣的事情,沒有編譯器警告或錯誤責任:

TypeToInstanceMap<Parser<?>> parsers; 

parsers.put(new TypeToken<Parser<String>>(){}, 
      makeStringParser()); 

Parser<Integer> intParser = parsers.get(new TypeToken<Parser<Integer>>(){}); 

這基本上是做非常相似@ruakh's answer引擎蓋下的東西的庫。

在Java 5發佈後不久,開發人員將<T>添加到Class<T>,Neil Gafter,discussed the fundamental issue in his blog。他呼籲Class<T>「類型標記」,並說:

[Y] OU根本無法做出型令牌泛型類型

...換句話說,你可以」 t做一個Class<Parser<T>>

+0

你分享的鏈接絕對是一個很好的閱讀,並有助於澄清這個話題。由於我正在尋找不需要第三方庫的解決方案,因此我決定接受@ ruakh的答案。話雖如此,我認爲這絕對是一個很好的選擇。 – fayerth 2015-02-11 01:53:12

0

我得到這個以不同的方式工作。我正在嘗試用仿製藥自己,並會很樂意收到批評:)

我所做的是爲Parseable對象添加標記接口,然後將其用作Parser的上界。

public interface IParseable {} 

public class Parser<T extends IParseable> { 
    T paresableObj; 
    // do something with parseableObject here 
} 

而現在解析器工廠不必使用通配符也不使用強制轉換。

public class ParserFactory { 

    private Map<Class<?>, Parser<? extends IParseable>> parsers = new HashMap<Class<?>, Parser<? extends IParseable>>(); 

    public <T> void addParser(Class<T> type, Parser<? extends IParseable> parser) { 
     if(parserFor(type) == null){ 
      parsers.put(type, parser); 
     }else{ 
      //throw some excep 
     } 
    } 

    private <T> Parser<? extends IParseable> parserFor(Class<T> type) { 
     return parsers.get(type); 
    } 

} 
+0

這並沒有幫助,因爲你強迫客戶現在處理'IP可清除'而不是默認爲'對象'的通配符。然而,解析器應該處理的所有內容都不可能實現該接口。所以這基本上是一個額外的負擔。 – SpaceTrucker 2015-02-10 09:52:12

+0

@SpaceTrucker - 謝謝你,你是對的。它的確如此。我認爲這是一個內部庫,在這種情況下,讓類實現標記接口並不困難。此外,我認爲解析任何對象是一個高難度的命令,而Parseable的對象應該具有某些特徵。不捍衛答案,只是解釋我的思路。再次感謝您的反饋。 – ramp 2015-02-10 10:25:42