Effective Java Item28 - 列表優於數組
December 08, 2018
這篇是Effective Java - Prefer lists to arrays章節的讀書筆記 本篇的程式碼來自於原書內容
Item28: 列表優於數組
我們來看看先講泛型跟數組的不同
covariant 協變 跟 invariant 不可變
Array 是協變 意思是說 如果Sub
是Super
的子類 那Sub[]
也是 Super[]
的子類
泛型 則是不可變 意思是說 任意兩個Type, List<Type1>
和 List<Type2>
不是彼此的子類型或是父類型
來個例子 以下的代碼compile會過
執行時才會在runtime拋出例外
但下面的程式在編譯時期就會出錯
當然這兩個用法都不對 只是用List會在compile-time直接出錯 好的IDE還直接跟你說為什麼錯
好處自然清楚 我們希望越早發現錯誤越好
Reified 具體化 vs Erasure 擦除
Array是具體化的 所以Array會在運行時 才去檢查元素類型的約束
泛型則是透過擦除來實現 這代表只在編譯期執行類型的約束 運行期則是丟棄(擦除)元素類型訊息
目的是要讓舊的沒有支援泛型的程式可以相容
難以混用
基於以上根本上的區別 數組和泛型很難混用 比如說
new List <E> []
new List <String> []
new E []
全都會拋出泛型數組創建錯誤
為什麼不能創建泛型Array呢 因為他不是type-safe 如果泛型Array合法 那就有可能會在運行時拋出ClassCastException
那就違反了泛型的保證
上例子
(1) 現在假設創建泛型Array合法
(2) 創建泛型
(3) 合法 因為array是協變, List<String>
是 Object
的subtype
(4) 合法 因為泛型是擦除, List<Integer>
在運行時就只是個List List<String>[]
在運行時就是個List[]
(5) 出大事了 右邊回傳Integer 左邊卻是String 拋出ClassCastException
違反了泛型的保證 所以索性第一步就compile-error
所以當你在可以選擇要使用List<E>
還是 E[]
的時候 通常前者是比較好的
簡潔性+性能 vs 類型安全性和互操作性
Trade-off 例子
來看一下一個隨機選擇器 choose()
會回傳choiceArray裡面一個隨機的東西
實在好懂 實在簡潔 麻煩的是當你調用choose()
你必須轉換你的類型
上面的程式compile會過 但很明顯的運行時會拋出ClassCastException 不是typesafe
現在來改改看 讓他成為泛型
不能被編譯 Constructor出錯
簡單 強制轉換 加上(T[])
從編譯錯誤 變成警告
編譯器告訴你在運行時不能保證強制轉換的安全性
除非你能保證使用者不亂用 那你可以照Item27的方法
加上註解抑制警告 但是Item27也說 那是逼不得已的做法
我們試著再改動一次
功德圓滿 這是最冗長也是運行起來最慢的版本 但卻是typesafe的版本
結論
數組和泛型有著非常不同的類型規則 數組是協變且可具體化 泛型是不可變且可被擦除
所以數組提供了運行期的typesafe而不是編譯期的typesafe
泛型提供了編譯期的typesafe而不是運行期的typesafe
所以當你在猶豫要選哪個時 選擇列表而不是數組