閒聊「Arrays.asList()」的坑
概要
「Arrays」是一個與陣列操作相關的工具類別;而「Arrays.asList()」是該類別中的一個方法,官方敘述如下:
簡單的說,它的功能就是將陣列轉成「List」,其屬於「static」方法。
而「坑」的故事,就從下面那段代碼說起:
其執行結果如下:
沒錯,就是拋出異常:「UnsupportedOperationException」,其意即當前的物件本身不支持「add()」方法。
因此,我們立刻去看「Arrays.asList()」所返回的物件到底是什麼,如下:
咦,是「ArrayList」?為什麼「ArrayList」不能執行「add()」操作?
本文
導致異常的原因
關於「概要」中的問題我們稍後再回答,在此之前,先了解「ArrayList」的基礎知識。
熟悉「Java」語言的人應該都知道,「List」僅是個「Interface」,而我們平常針對「ArrayList」執行「add()」方法,其實是「ArrayList」本身的實作:
所以「ArrayList」的「add()」應該是具有功能的,但為什麼在「概要」中的程式範例執行卻會拋出「UnsupportedOperationException」異常呢?
其原因是:「Arrays.asList()」中所返回的是「java.util.Arrays.List」,而它與「java.util.ArrayList」是不同類別,稍微打印一下就知道了,如下:
首先,再仔細的看一次「Arrays.asList()」的實現,如下:
它使用的是「Arrays」的靜態內部類別「Arrays.ArrayList」,如下:
而不是「java.util」下的「ArrayList」類別,如下:
雖然他們都是繼承「AbstractList」的類別,但其在實作上是有不小的差異,例如「Arrays.ArrayList」是靜態內部類,又如「ArrayList」具「Cloneable」標記。
但其中與「概要」中的問題最相關的差異在於:「Arrays.ArrayList」並沒有實現「add()」方法,「Arrays.ArrayList」的源碼如下:
事實上,不僅是「add()」,「remove()」也是;所以我們可以推測其方法實作應該來自其父類別:「AbstractList」,如下:
看到實作,就瞬間都懂了吧?
因為「Arrays.ArrayList」的「add()」方法實作是來自於「AbstractList」,而「AbstractList」的實作就是直接丟出「UnsupportedOperationException」異常。
解決方式
原理懂了,但程式設計師關注的是:如何能讓「Arrays.ArrayList」變成可以操作「add()」和「remove()」的「ArrayList」?
首先會想到的是轉型:
一經執行,如下:
轉型失敗,拋出「ClassCastException」。
其結果很正常,雖然它們皆繼承「AbstractList」,但是「Arrays.ArrayList」與「ArrayList」並沒有直接的繼承關係。
既然不能轉型,那更換容器總可以吧,代碼如下:
執行結果如下:
沒錯,我們可以藉由「ArrayList」的與「Collection」相關的多載建構子來達到目的。
意料之外的「remove()」
事實上,當時有個意外的小插曲,代碼如下:
執行結果如下:
是的,沒有丟出異常,很意外吧?
事實上,這是因為「AbstractList」中的「remove()」其傳入參數是「int」,如下:
而傳入參數為「物件」的「remove()」是由「AbstractList」的父類:「AbstractCollection」所實作,如下:
而在本小節的範例中,由於「remove()」傳入的參數是字串:「Sunday」,然而「Sunday」並不存在於「weekDayList」中,因此「remove()」在執行到「equals()」就被排除了,故不會產生錯誤;但若傳入的是其它確實存在於「weekDayList」的物件或是「int」,那麼依然會在執行時拋出異常。
題外話,事實上,這篇也是舊文章翻寫,而當時會發現這件事情,是因為要移除的字串拼錯,然後就…,碼農的踩坑日常阿。
總結
在實際開發上,「Arrays.asList()」是個相當好用的類別,相較於傳統的「陣列」,「List」類具有更好的可操作性,因此我們常會有需求是要將「陣列」轉成「List」,尤其是「ArrayList」。
或許部份人會認為,這是一個「for loop」就能解決的事情;沒錯,但就算撇開效率不說,更令人在意的是,它不優雅,甚至有點愚蠢。
至於為什麼在「Arrays」要另外實作一個「Arrays.ArrayList」,而不用一般的「ArrayList」?
關於該問題,筆者並沒有找到任何有關於官方的回應或說明,但是在「StackOverFlow」上是有相關的討論串的,例如在「Why does Arrays.asList() return its own ArrayList implementation」一文中,有網友就認為可能原因是性能、效率,又或是說它本來預期就為「View List」,如下:
但不論確實原因為何?總之,在使用時要注意就是。