java集合使用注意事項
集合判空
判斷所有集合內部的元素是否為空,使用 isEmpty()
方法,而不是 size()==0
的方式。
集合轉 Map
在使用 java.util.stream.Collectors
類的 toMap()
方法轉為 Map
集合時,一定要注意當 value 為 null 時會拋 NPE 異常。
集合遍歷
不要在 foreach 迴圈裡進行元素的 remove/add
操作。remove 元素請使用 Iterator
方式,如果併發操作,需要對 Iterator
物件加鎖。
fail-fast
https://juejin.cn/post/6879291161274482695#heading-2
fail-fast的字面意思是“快速失敗”。當我們在遍歷集合元素的時候,經常會使用迭代器,但在迭代器遍歷元素的過程中,如果集合的結構被改變的話,就會丟擲異常,防止繼續遍歷。這就是所謂的快速失敗機制。
private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount;罪魁禍首是checkForComodification這個方法,它是在modCount != expectedModCount的時候丟擲了異常,而在next方法中第一句就是checkForComodification,所以遍歷集合才會可能丟擲併發修改異常。 在建立一個迭代器後,expectedModCount的初始值就是modCount了,對集合修改只會改變modCount,expectedModCount只會在迭代器的remove方法中被修改為modCount,稍後詳細講解迭代器的方法public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
如何避免fail-fast拋異常
fail-safe
ArrayList使用fail-fast機制自然是因為它增強了資料的安全性。但在某些場景,我們可能想避免fail-fast機制丟擲的異常,這時我們就要將ArrayList替換為使用fail-safe機制的CopyOnWriteArrayList。
採用安全失敗機制的集合容器,在 Iterator 的實現上沒有設計丟擲 ConcurrentModificationException 的程式碼段,從而避免了fail-fast。
最後介紹下典型採用fail-safe的容器——CopyOnWriteArrayList~
寫時複製,簡單理解就是,當我們往一個容器新增元素的時候,先將當前容器複製出一個新的容器,然後新的容器裡新增元素,新增完元素之後,再將原容器的引用指向新的容器。
這樣做的好處是我們可以對CopyOnWrite容器進行併發的讀,而不需要加鎖,因為當前容器不會新增任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。
除了上面介紹的直接使用 Iterator
進行遍歷操作之外,你還可以:
- 使用普通的 for 迴圈
- 使用 fail-safe 的集合類。
java.util
包下面的所有的集合類都是 fail-fast 的,而java.util.concurrent
包下面的所有的類都是 fail-safe 的。
fail-safe與fail-fast的區別
當我們對集合結構上做出改變的時候,fail-fast機制就會丟擲異常。但是,對於採用fail-safe機制來說,就不會丟擲異常(大家估計看到safe兩個字就知道了)。
這是因為,當集合的結構被改變的時候,fail-safe機制會在複製原集合的一份資料出來,然後在複製的那份資料遍歷。
因此,雖然fail-safe不會丟擲異常,但存在以下缺點
- 複製時需要額外的空間和時間上的開銷。
- 不能保證遍歷的是最新內容
集合去重
可以利用 Set
元素唯一的特性,可以快速對一個集合進行去重操作,避免使用 List
的 contains()
進行遍歷去重或者判斷包含操作
集合轉陣列
使用集合轉陣列的方法,必須使用集合的 toArray(T[] array)
,傳入的是型別完全一致、長度為 0 的空陣列。
陣列轉集合
使用工具類 Arrays.asList()
把陣列轉換成集合時,不能使用其修改集合相關的方法, 它的 add/remove/clear
方法會丟擲 UnsupportedOperationException
異常