泛型方法的反模式

Java譯站發表於2015-05-11

我承認,我自己也忍不住用過這項技術。它簡直太方便了,可以省去一次不必要的型別轉化。這就是:

interface SomeWrapper { <T> T get(); }

現在你便可以安全地將包裝類轉換成任意型別了:

SomeWrapper wrapper = ... // Obviously Object a = wrapper.get(); // Well... Number b = wrapper.get(); // Risky String[][] c = wrapper.get(); // Unprobable javax.persistence.SqlResultSetMapping d = wrapper.get();

這也正是你在使用時所用到的API,jOOR是我們開發並開源的一個反射工具庫,它可以提升我們編寫整合測試的效率。有了jOOR,你可以這麼編寫程式碼:

Employee[] employees = on(department) .call("getEmployees").get(); for (Employee employee : employees) { Street street = on(employee) .call("getAddress") .call("getStreet") .get(); System.out.println(street); }

這個API非常簡單。on()方法會對某個物件或類進行封裝。call()方法會通過反射去呼叫這個物件上的方法(不需要簽名正確,也不需要方法宣告成public,同時也不會丟擲任何的受檢查異常)。同時你也不需要強制型別轉換,便能通過get()方法將結果轉換成任意的型別。

對於一款像jOOR這樣的反射庫而言,這麼做是沒問題的,因為這個庫本身就不是型別安全的。當然不會安全了,因為本來就是反射。

不過這還是會讓人感到有些不爽。感覺就是在呼叫點這裡承諾會返回某個型別,但實際上卻無法兌現,這很可能會導致ClassCastException異常——在有了Java 5以及泛型之後才開始寫Java的開發人員是很難體會到這種感覺的。

但JDK也是這麼做的。。

沒錯,JDK是這麼幹了。不過這樣的情況並不多,並且只是在泛型引數的型別並不那麼重要的情況下才會這麼做。比如說,當你通過Collection.emptyList()獲取一個空列表的時候,這個方法的實現是這樣的:

@SuppressWarnings("unchecked") public static final <T> List<T> emptyList() { return (List<T>) EMPTY_LIST; }

沒錯,EMPTY_LIST從List強轉成List是挺不安全的。但從語義層面來說,這樣的強轉是安全的。你不能修改這個列表,因為這是個空列表。List中也不會有任何一個方法會返回給你一個非目標型別的T或者T[]的例項。因此,這些做法都是合法的:

// perfectly fine List<?> a = emptyList(); // yep List<Object> b = emptyList(); // alright List<Number> c = emptyList(); // no problem List<String[][]> d = emptyList(); // if you must List<javax.persistence.SqlResultSetMapping> e = emptyList();

JDK的開發人員從來都會非常小心地不去對你所可能獲取到的泛型型別做出任何無法兌現的承諾。也就是說,即便存在一個更適合的型別的時候,通常返回給你的也都是Object型別。

哪個型別更適合只有你知道,編譯器可不瞭解。型別擦除是有代價的,代價就是當包裝類或者集合為空的時候。像這樣的一個表示式編譯器是無法得知它的型別的,它可不能不懂裝懂。也就是說:

不要使用這種只為了省一次型別轉化的泛型方法的反模式。

相關文章