閒聊「Arrays.asList()」的坑

Programing Language:Java Basics

RICK
Feb 25, 2021

概要

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」,如下:

但不論確實原因為何?總之,在使用時要注意就是。

--

--

RICK
RICK

Written by RICK

當遇到重開機無法解決的 BUG 時,那就試試關機吧。

No responses yet