[每日短篇] 25 - 如何解決 Java 泛型類型轉換時的警告

問題

平常在寫 Java 代碼時對警告 Type safety: Unchecked cast from XXX to YYY 必定不會陌生,例如 Type safety: Unchecked cast from Object to Map<String,String>。若是仔細觀察的話,能夠注意到,YYY 歷來不會是一個非泛型的類型。java

緣由

產生這個警告的緣由是在強制類型轉換時目標類型是一個非無邊界通配符的泛型類型,而 Java 的半殘泛型又沒法在運行時判斷一個泛型類型是否匹配目標類型。舉個例子來講就是:工具

Object source = Collections.singletonMap("a", 2);  // #1
var target = (Map<String, String>) source;         // #2
var value = target.get("a");                       // #3

對於上面的代碼,沒法在 #2 行代碼執行時就確認 target 是一個徹底經過類型檢查的值,當 #3 行代碼執行時,仍是會產生 java.lang.ClassCastException 異常;而從直覺上,若是 #2 行沒有編譯期錯誤或者運行時 java.lang.ClassCastException 異常,那 target 就是 Map<String, String> 類型的。在這個矛盾之下,在 #2 處產生 Type safety: Unchecked cast 警告提醒該行可能有問題,也是一種沒有辦法的辦法。編碼

前面提到了非無邊界通配符的泛型類型,若是目標類型是無邊界通配符的泛型類型,例如:code

Object source = Collections.singletonMap("a", 2);  // #1
var target = (Map<?, ?>) source;                   // #2

上面代碼中的 #2 行不會產生警告,由於這將指定具體的泛型參數類型這件事推遲到了後面的代碼中,在這裏只須要肯定 sourceMap 類型便可。ip

解決

1. 解決提出問題的人

最容易想到的,也是最廣泛採用的方法天然是——當解決問題有困難時,解決提出問題的人。固然,我這是開個玩笑,雖然你們都知道編碼實踐中這纔是主流。get

例如在 Eclipse 中,這個警告受 Preferences > Java > Compiler > Errors/Warnings > Generic types > Unchecked generic type operation 選項控制,將其關閉便可。io

對這個警告來講,因爲它很是特定於泛型類型的轉換,而且在這個問題上是總體沒有好的解決方案,所以關掉也不會有什麼實質性的影響。可是由於沒有通用的關掉警告的方法,這種不可移植的方案難以讓人接受。編譯

另外,@SuppressWarnings("unchecked") 也可讓提出問題的人閉嘴。我但願它是被用在變量上面而不是方法上甚至模塊上。使用這個註解的問題是,當對代碼質量要求很高時,這個註解一般是被配置爲忽略的,而試圖消除警告的場景又每每與之高度重合。ast

2. 嚴謹的處理辦法

前面提到了,無邊界通配符類型由於延遲了類型指定而完全符合了要求,咱們能夠把類型轉換拆成多步,保證每一步都只轉換肯定的類型。固然,在這個場景下這叫沒有困難創造困難也要上。class

Object source = Collections.singletonMap("a", 2);  // #1
var target = (Map<?, ?>) source;                   // #2
Object t = target.get("a");                        // #3
                                                   // #4
if (t instanceof String) {                         // #5
    String value = (String) t;                     // #6
}                                                  // #7

其中的繁瑣和侷限性一看便知。須要特別注意的是 #3= 的右邊失去了強類型的好處,彷佛有些得不償失。

3. 一個還算不錯的處理方式

從前面給出的緣由能夠看到,在眼下以及可預見的將來,都沒有簡單而完全的解決方法。好在這個警告主要是給有代碼潔癖人帶來麻煩,能夠用一種代碼級的方法幾乎完整解決問題。只須要寫一個工具類

public final class CastUtils {

    @SuppressWarnings("unchecked")
    public static <T> T cast(Object obj) {
        return (T) obj;
    }

    private CastUtils() {
        throw new UnsupportedOperationException();
    }

}

原來有警告的代碼改成

Object source = Collections.singletonMap("a", 2);           // #1
Map<String, String> target1= CastUtils.cast(source);        // #2
var target2 = CastUtils.<Map<String, String>>cast(source);  // #3
var value1 = target1.get("a");                              // #4
var value2 = target2.get("a");                              // #5

注意 #2 行、#3 行的不一樣寫法,它們都能消除警告,可是 #2 寫法更短,在絕大多數場景下更優一點。

CastUtils 類中依然使用到了 @SuppressWarnings("unchecked") 註解,若是該註解沒有被配置爲忽略,就能徹底消除該警告,若是被配置爲忽略,那也能夠保持整個代碼中僅有 1 處該警告,也就是幾乎完整解決了問題。固然,還有另一種選擇,將 CastUtils 置於某個工具類庫 jar 文件中,再把 jar 文件引用到項目中,不管 @SuppressWarnings("unchecked") 是否被配置爲忽略,均可以完全消除該警告。

總結

Type safety: Unchecked cast 是個說嚴重不嚴重,可是對整潔代碼來講卻不能無視的警告。若是本身有工具類庫,在工具類庫裏增長 CastUtils 工具類是最好的選擇。次優的選擇則是將 CastUtils 工具類直接置於項目代碼中。

相關文章
相關標籤/搜索