泛型方法的反模式
譯文出處: 花名有孚 原文出處:blog.jooq.org
我承認,我自己也忍不住用過這項技術。它簡直太方便了,可以省去一次不必要的類型轉化。這就是:
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類型。
哪個類型更適合只有你知道,編譯器可不了解。類型擦除是有代價的,代價就是當包裝類或者集合為空的時候。像這樣的一個表達式編譯器是無法得知它的類型的,它可不能不懂裝懂。也就是說:
不要使用這種只為了省一次類型轉化的泛型方法的反模式。