1. 程式人生 > 其它 >java集合使用注意事項

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;

    
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(); } }
罪魁禍首是checkForComodification這個方法,它是在modCount != expectedModCount的時候丟擲了異常,而在next方法中第一句就是checkForComodification,所以遍歷集合才會可能丟擲併發修改異常。 在建立一個迭代器後,expectedModCount的初始值就是modCount了,對集合修改只會改變modCount,expectedModCount只會在迭代器的remove方法中被修改為modCount,稍後詳細講解迭代器的方法

如何避免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不會丟擲異常,但存在以下缺點

  1. 複製時需要額外的空間和時間上的開銷。
  2. 不能保證遍歷的是最新內容

 

 

 

集合去重

可以利用 Set 元素唯一的特性,可以快速對一個集合進行去重操作,避免使用 List 的 contains() 進行遍歷去重或者判斷包含操作

集合轉陣列

使用集合轉陣列的方法,必須使用集合的 toArray(T[] array),傳入的是型別完全一致、長度為 0 的空陣列。

陣列轉集合

使用工具類 Arrays.asList() 把陣列轉換成集合時,不能使用其修改集合相關的方法, 它的 add/remove/clear 方法會丟擲 UnsupportedOperationException 異常