別再這樣使用List了,會坑到你哭!

👉本文章全部文字純原創,若是須要轉載,請註明轉載出處,謝謝!😘java

哈嘍,各位朋友您們好,沒想到這麼快又跟你們見面了,由於此次寫這篇文章又徹底是一個偶然的機會——一次無心的踩坑,這坑倒不是什麼特別牛逼深刻的東西,不少大佬們估計都知道,說出來還有點low,可是我以前並不知道......😭因此浪費了很多時間,比較鬱悶。再加上我相信確定仍是有一些朋友是不知道的,因此這一切都導致我想寫點什麼,幫助後來人避免踩坑。git

問題描述

估計不少人都知道Arrays這個工具類吧?顧名思義,這個工具類中都是封裝對Java數組對象的操做,好比二分查找、拷貝數組、排序、流操做等很是實用功能,可是我今天要說的是其中的asList()方法。這個方法能夠很輕鬆方便地將一個可變參數(本質就是數組)轉換成一個List,相信不少朋友也和我同樣這樣使用,畢竟這樣比先new一個List再往裏面添加元素要方便不少。之前我也一直是這樣想的,一直這樣偷懶着用,舒服~😎常在河邊站,哪有不溼鞋,直到昨天......github

昨天閒得蛋疼,無心中寫了以下代碼,沒想到這一寫,寫出來了個猝不及防......面試

List<String> ls = Arrays.asList("1", "2", "3");
//MetaObject是一個Mybatis裏面的一個封裝對象的工具類,封裝後可使用相似OGNL同樣操做對象的屬性,這個我從Mybatis裏單獨提取出來放github上了,有興趣的能夠玩玩。
//這個不是今天的重點,此處能夠忽略。
MetaObject metaObject = SystemMetaObject.forObject(ls);
metaObject.add("4");
System.out.println(ls);
複製代碼

上面這段代碼貌似一看沒啥問題,就是往一個List中add一個新元素唄,這能有啥問題啊?結果報錯了......😭算法

al-1

報錯了沒啥稀奇的,學Java的要是沒報過幾個Bug都很差意思說本身是搞技術的,但坑爹的是,這錯有必定的迷惑性。其實看報錯提示,若是是細心的人就會產生一個疑問?報錯的位置竟然是在AbstractList.add()!?震驚!沒錯,在AbstractList.add()這裏確實啥也沒實現,只會報出該異常,該方法是須要由各實現類各自實現的。後端

那爲何錯誤會從這個地方拋出來的?Arrays.asList()這個玩意返回的究竟是個什麼List類型啊?設計模式

al-2

深刻源碼分析

因而,我天然是絕不猶豫地進入Arrays.asList()方法源碼看,以下,奇怪,這不是一個ArrayList嗎?但是ArrayList.add()是實現了AbstractList.add()的方法的啊,怎麼還會報這個錯呢?數組

al-3

就這麼一個極其簡單,過後回想起來都以爲很無語的一個問題,整整困了我半個多小時啊......數據結構

其實這個問題至關的簡單,若是這個時候能再往裏深刻點一下,就知道其中的坑爹之處,其實此ArrayList非彼ArrayList......尼瑪,這裏面的這個ArrayList類只是Arrays類中的一個私有靜態內部類。這個類確實繼承了AbstractList類,可是並無去實現其add()方法,因此會直接拋出UnsupportedOperationException。其實這個問題至關簡單,不過既然被這個類坑了,索性咱們就再全面瞭解一下這個類的內容吧。框架

al-4

其實,這個類中只是實現了AbstractList的一部分方法,並且絕大部分方法都是讀取操做,好比上面說的add()操做並未實現,天然也就失去了ArrayList該有的動態伸縮的功能。所以,不可貴出結論,之前爲了偷懶一直使用的Arrays.asList()確實能夠返回一個List類型,可是這個List長度並不可變,無法像ArrayList那樣作到動態伸縮,實現了set()方法能夠作修改操做,但也僅限於你傳入的數組範圍,超事後會像數組同樣拋出java.lang.ArrayIndexOutOfBoundsException。因此本質上返回的其實仍是一個數組。

mmp......太坑爹了,那麼設計JDK的大佬爲什麼要設計這麼一個坑爹的玩意呢?專坑我這樣的菜鳥......😭不過鬱悶歸鬱悶,仍是要搞清楚這樣設計的初衷啊不是?其實,Arrays.asList()這個方法的真正意義是在於鏈接Java中的數組對象和Java集合的API,跟Collection.toArray()是一對兄弟。他們的關係以下圖表示。

al-9

因此,若是咱們須要將一個數組(或者可變參數)轉成一個List,正確操做姿式是以下代碼所示:

List<String> ls = Arrays.asList("1", "2", "3");//或者是new String[]{"1", "2", "3"}
List<String> ls2 = new ArrayList<>(ls);
ls2.add("4");
System.out.println(ls2);
//輸出結果:
[1, 2, 3, 4]
複製代碼

哈哈,這時你獲得的就是一個真正的ArrayList了,天然全部List相關操做都能正常執行了,其實這裏面能夠引伸出一種設計模式——適配器模式,這一點在阿里巴巴的《Java開發手冊》中也有所說起。固然這邊不是講設計模式,不展開,後續會開啓一系列關於設計模式的文章,但願到時你們期待。這邊客戶端須要使用當前的接口(List)去對原有不兼容的接口(數組)操做,而Arrays.asList()剛好充當了適配器的角色,爲數組和集合的轉換提供了一個橋樑。實際上是本身用錯該方法,錯怪設計JDK API的大佬了......🤦‍♂️

知識延伸

既然說到了List,不妨再給你們引伸個內容,咱們發現Arrays$ArrayList除了繼承了AbstractList,以及實現了咱們熟悉的java.io.Serializable,還另外實現了一個RandomAccess接口,我就順便聊聊這玩意,畢竟我之前沒了解過這玩意,估計大部分人也和我同樣。點進去會發現,這個接口是個標記接口,裏面啥東西都沒有,JavaDoc卻是寫了不少。基本大意是說,這個標記接口用來聲明能夠快速隨機訪問的List。這邊就引伸出2個問題,

什麼是隨機訪問的List?用這個接口聲明爲隨機訪問的List有什麼卵用?

學過List的都知道,經常使用的List主要有兩個:ArrayList和LinkedList。這二者的區別相信不用我再贅述了,這也是面試題的高頻選擇。這二者最大的區別就是底層實現。ArrayList採用數組實現,而LinkedList採用鏈表。有點數據結構基礎的人都知道數組在內存中是連續分配的,全部元素緊密排列,咱們可使用數組索引快速定位到該數組中任意一個元素。這種結構特色致使數組結構查詢效率極高,時間複雜度O(1)。鏈表就恰好相反,每一個元素使用節點保存,節點和節點之間採用指針鏈接,因此要查詢鏈表中某一元素,必須從表頭元素依次向後遍歷,致使其時間複雜度最差O(n)。

就是由於ArrayList和LinkedList的實現本質存在如此大的差別,因此聰明的JDK設計者在JDK API設計有關他們的算法時就會區別對待,這中間就是使用到了RandomAccess接口。

咱們先來看看ArrayList和LinkedList的源碼,細心的人會發現,ArrayList實現了RandomAccess,而LinkedList並無,這是爲何呢?這就跟我前面說的二者的差別性有關係了。

al-5

al-6

所謂的隨機訪問,就是指像數組這種結構,不管須要查找其中哪一個元素,這個元素出於什麼位置,都是O(1)複雜度,效率很是高。這類List稱之爲能夠隨機訪問的List,如ArrayList。而LinkedList則顯然不是。

那用這個接口聲明爲隨機訪問的List到底有什麼卵用呢?這只是一個空接口而已啊?

這裏先跟你們聊一下標記接口。標記接口也叫空接口,顧名思義,這些接口不包含任何方法和屬性,僅僅做爲標記使用,那究竟是標記啥?標記它屬於某一個特定的類型。Java中最典型的標記接口有*java.io.Serializablejava.lang.Cloneable。標記接口的惟一目的就是後續在一些算法中可使用instanceof進行類型查詢。固然在JDK引入註解以後,這個功能也能夠被標記註解替代了。同理,標記註解不包含成員。標記註解的惟一目的就是標記聲明,後續可使用isAnnotationPresent()*方法進行查詢。

RandomAccess接口到底在哪裏有使用到?

Collections是JDK中一個很是實用的集合操做工具類,裏面封裝實現了大量對Java集合框架的操做,包括查找、排序、遍歷、比較等功能。在這個類中,咱們能夠發現有大量RandomAccess接口的身影。咱們以其中的binarySearch()方法爲例,其他的方法你們有興趣能夠本身研究。

al-7

這是一個二分搜索方法。能夠清晰的看出,這邊使用RandomAccess接口來區別不一樣的List類型,並執行不一樣的算法。具體的算法實現不是咱們這邊的重點。不可貴出結論,當一個List若是是實現了RandomAccess(好比ArrayList)或者元素個數<5000,就會採用索引方式遍歷查找。不然會採用迭代器從頭至尾遍歷方式搜索。

其實RandomAccess接口的JavaDoc也提到了,若是是實現了RandomAccess接口的List,通常狀況下采用索引遍歷的性能會比直接用迭代器好一些,固然這是理論上的支持,具體實際測試狀況,你們有興趣能夠本身性能測試一下結果,這邊就再也不贅述。

結束

從下次開始,我將會開始寫GOF23全部設計模式一整個系列文章,其實以前我也作過一些筆記放在github上,不過有讀者反映筆記太過精簡,看不懂,並且我的以爲記筆記終歸比不上文章來的深刻全面,因此此次打算將其豐滿成一系列文章,爲保證質量,保持在一週到十天左右一篇的頻率,敬請期待。😊


  • 今天的技術分享就分享到這裏,感謝您百忙抽出這麼長時間閱讀個人文章😊。
  • 另外,個人筆記還有文章也會在個人github上更新。
相關文章
相關標籤/搜索