之前寫過一篇ArrayList原始碼的博客 https://www.cnblogs.com/zumengjie/p/13538394.html 其中遺留了一個問題,ArrayList添加元素和洗掉元素或者清空元素時都會有一個操作 modCount++;當時并沒有將死磕到底的精神進行到底,這兩天在一本原始碼書籍里邊提到了這個變數,看完后決定記錄下來,這本書我也介紹大家去讀《Java修煉指南-高頻原始碼決議》是開課吧組編的書,這本書四個字形容,開卷有益,
下文帶//為個人注釋,源代碼使用這個顏色
迭代集合引發的問題
Itr迭代器
ListItr迭代器繼承自Itr
ListItr的特色功能
Fail-Fast機制
一些有趣的方法
-
forEach
-
trimToSize
-
subList
-
為什么說增強for就是迭代器?
-
Arrays.asList
迭代集合引發的問題
首先先看一段代碼,這段代碼就是在for回圈中使用集合本身的remove方法洗掉元素,結果大家也都知道因為洗掉了元素,size減去1,變數i又自增1,最終導致被元素后邊的一個沒有被遍歷到,B被洗掉了,C頂替了B它的index是2但是變數i已經是3了所以跳過,
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
// 結果是 A D E
for (int i = 0; i < list.size(); i++) {
if (i == 1) {
list.remove(i);
} else {
System.out.println(list.get(i));
}
}
View Code
接著再看這一段代碼,看這段代碼主要就是要知道在迭代器中使用集合本身的洗掉,新增都會報錯,ConcurrentModificationException,
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next.equals("B")) {
list.remove("B");// 報錯 java.util.ConcurrentModificationException
list.remove(1);// 報錯 java.util.ConcurrentModificationException
list.add("F");// 報錯 java.util.ConcurrentModificationException
} else {
System.out.println(next);
}
}
View Code
但是可以使用迭代器自己的新增或者洗掉元素的方法,
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next.equals("B")) {
iterator.remove();
} else {
System.out.println(next);
}
}
View Code
Itr迭代器
//它回傳的是一個Iterator物件是ArrayList中的一個內部類,Iterator<E> iterator()其實是List介面的一個抽象方法,
public Iterator<E> iterator() {
return new Itr();
}
int cursor;//指向下一個元素的下標,默認是0,
int lastRet = -1;//上一個被迭代的下標,
int expectedModCount = modCount;//這個變數在集合長度變化時會自增1
public boolean hasNext() {
return cursor != size;//判斷有沒有下一個元素的依據是cursor的size!=size,cursor默認是0,如果size是3,cursor也是3那就沒有下一個元素了,
}
//在迭代器里拋出的例外就是它做的,當迭代器創建后expectedModCount==modCount,但如果我們使用集合本身的方法改變了集合的長度modCount就會增加而expectedModCount不會增加,
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
@SuppressWarnings("unchecked")
public E next() {//獲取集合內的下一個元素
checkForComodification();//進來后就走了判斷,如果通過迭代器以外的方法修改了集合的長度,那這個方法就直接例外了,
int i = cursor;//默認是0
if (i >= size)//這里也是健壯性的判斷,和hasNext()起到同樣的效果,
throw new NoSuchElementException();
Object[] elementData = https://www.cnblogs.com/zumengjie/archive/2020/12/17/ArrayList.this.elementData;//拿到ArrayList中的陣列,
if (i >= elementData.length)//健壯性的判斷
throw new ConcurrentModificationException();
cursor = i + 1;//cursor+1,這表示下一個被讀取的元素的下標,
return (E) elementData[lastRet = i];//獲取當前元素,并且把下標賦值給lastRet,也就是說這個變數的意思是上一個被迭代的下標,
}
public void remove() {//洗掉當前被迭代的元素
if (lastRet < 0)
throw new IllegalStateException();//lastRet默認是-1,也就是說如果沒有呼叫next()就呼叫remove()是會報錯的,
checkForComodification();//檢驗modCount
try {
ArrayList.this.remove(lastRet);//此處呼叫了ArrayList自身的remove(index)
cursor = lastRet;//這里有點意思,假如呼叫next()獲取的元素是第四個,其實cursor是5而lastRet是4,這里把lastRet賦值給cursor意味著下次next()回傳的結果還是第四個,
lastRet = -1;//這里將lastRet置為-1表示如果連續呼叫兩個remove()也會報錯,
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//這個方法其實就是用來遍歷集合的,不要試圖使用這個方法時改變集合長度,否則極大的幾率會出現例外,
//另外這個方法中使用的cursor,lastRet和迭代器中其他方法是共用的,
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);//斷言判斷
final int size = ArrayList.this.size;//當前集合的長度
int i = cursor;//cursor默認是0
if (i >= size) {//健壯性判斷
return;
}
final Object[] elementData = https://www.cnblogs.com/zumengjie/archive/2020/12/17/ArrayList.this.elementData;//拿到陣列
if (i >= elementData.length) {//健壯判斷
throw new ConcurrentModificationException();
}
//這里就是遍歷集合元素,把當前元素交給引數consumer消費,
//如果你在消費時使用集合本身的增加或者洗掉方法,其效果和普通for回圈一樣,
//如果你在消費時使用迭代器的remove(i)則會直接報錯,因為lastRet默認是-1,
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
//這里修改cursor意味著再次呼叫next將會回傳false,
cursor = i;
lastRet = i - 1;
//這里會再次檢驗modCount
checkForComodification();
}
ListItr迭代器繼承自Itr
public ListIterator<E> listIterator() {
return new ListItr(0);//它創建的是帶引數的 Listltr(0)
}
ListItr(int index) {
super();
//傳入的index賦值給了cursor,這意味著我們可以手動的指定下一次迭代的位置,
//但是需要注意cursor更改后,lastRet沒有跟著更改,意味著創建listIterator呼叫迭代器的remove()同樣會例外,
cursor = index;
}
//獲取迭代器下一次迭代的下標,
public int nextIndex() {
return cursor;
}
//在Itr類中是沒有add()方法的,
public void add(E e) {
checkForComodification();//進來后首先做的就是檢驗modCount的值,
try {
int i = cursor;//當前下標賦值變數
//呼叫ArrayList自己的添加元素方法,將元素添加到當前i的位置,
//如果當前next元素是1,那么next后就是2也就是在當前迭代元素后添加元素,
ArrayList.this.add(i, e);
//迭代下標+1,這個操作意味著,新添加的元素不會被迭代到,
//例如當前集合為[A,B,C,D,E]當前next指向的是B,則next后cursor就是2
//在2位置添加元素后就是[A,B,N,C,D,E],那其實下一個next就是N但是cursor+1后下一個就是C了,
cursor = i + 1;
lastRet = -1;//添加后同樣會lastRet同樣是-1意味著同時呼叫兩次add(e)也會報錯,
expectedModCount = modCount;//將modCount賦值給expectedModCount,
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//這個set方法,倒是有些多余了,因為ArrayList集合本身的set方法不會更改modCount
//因為ListItr的set方法還會檢驗modCount的值,
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();//檢驗modCount
try {
ArrayList.this.set(lastRet, e);//ArrayList自身的set()方法,
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
ListItr的特色功能
//判斷有沒有上一個,默認的cursor是0,使用了next()后cursor+1,意味著有上一個元素,
public boolean hasPrevious() {
return cursor != 0;
}
//如果有上一個則呼叫previous獲取上一個元素,
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();//先檢驗modCount
int i = cursor - 1;//當前cursor左移
if (i < 0)//如果當前迭代器的cursor已經在最左邊了,就要報錯,
throw new NoSuchElementException();
Object[] elementData = https://www.cnblogs.com/zumengjie/archive/2020/12/17/ArrayList.this.elementData;//拿到集合中的陣列
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];//這里取得是cursor-1
}
//或者可以使用previousIndex向左移動cursor,但是總感覺這個方法使用不當會很危險,
public int previousIndex() {
return cursor - 1;
}
Fail-Fast機制
以上內容解釋了modCount這個變數到底是干啥用的,但是都沒有用很專業很有B格的名詞嘮一下,
Fail-Fast是Java中一種錯誤檢測機制!它的觸發機制是,在迭代程序中如果集合的結構發生了
變化此時迭代器并不知情或沒有來得及反應就會產生Fail-Fast事件,拋出ConcurrentModificationException例外,
所以modCount和迭代器中的expectedModCount不相等就會報錯這就是Fail-Fast機制,
一些有趣的方法
forEach
//這個方法是JDK8出的,為了結合拉姆達運算式使用,
//它接收一個Consumer消費者介面,這個介面只有一個accept方法,
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;//獲取modCount
@SuppressWarnings("unchecked")
final E[] elementData = https://www.cnblogs.com/zumengjie/archive/2020/12/17/(E[]) this.elementData;//獲取陣列
final int size = this.size;//獲取集合長度
for (int i=0; modCount == expectedModCount && i < size; i++) {//回圈陣列
action.accept(elementData[i]);//將陣列的每個元素交給Consumer的accept方法并且執行,
}
if (modCount != expectedModCount) {//此處也會判斷如果modCount出現改變會報錯,
throw new ConcurrentModificationException();
}
}
trimToSize
//這個方法用來收縮陣列,當已經確認了集合內不會添加元素時,呼叫此方法將集合內
//的陣列進行收縮,也就是說將陣列內的元素拷貝到新的陣列中,新的陣列大小和size相同
public void trimToSize() {
//這個變數現在已經很熟悉了吧,
modCount++;
if (size < elementData.length) {
//如果size是0就把空陣列賦值給elementData,如果不是就做陣列拷貝,
elementData = https://www.cnblogs.com/zumengjie/archive/2020/12/17/(size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
subList
看下圖的執行結果截取后的小集合元素為BC,小集合添加一個元素后衛BCF,令人迷惑的是大集合的元素ABCFDE,

首先subList(int,int)是對集合做截取回傳的一個List但是注意,回傳的集合并不是ArrayList而是SubList,
public List<E> subList(int fromIndex, int toIndex) {
//這個方法是對入參進行限定起始位置不能小于0,結束位置不能大于大集合的size,其實位置不能大于結束位置,否則都會拋出例外,
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
//這是ArrayList中的一個內部類,它繼承自AbstractList也就是說它和ArrayList是兄弟關系,
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
//這個變數和offset是一致的,要截取陣列的起始位置是N那么小集合和大集合的偏移量就是N
//也就是說要獲取小集合的M下標就是要到大集合中找N+M位置的元素
private final int parentOffset;
private final int offset;
int size;
//以上圖為例建構式引數分別是當前ArrayList,0,1,3
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;//這個值是1
this.offset = offset + fromIndex;//這個值是0+1
this.size = toIndex - fromIndex;//這個值是2
this.modCount = ArrayList.this.modCount;//ArrayList里的集合運算元變數,到處都有它,
//這是AbstractList中的add方法我們呼叫的是這個,而它則呼叫自己的雙引數方法也就是呼叫了子類自己的,
public boolean add(E e) {
add(size(), e);
return true;
}
//這是SubList里的add方法.傳入的引數是自己的size也就是2
public void add(int index, E e) {
//這里進行了檢驗index不能小于0不能大于size
rangeCheckForAdd(index);
//這個方法依然檢驗的是modCount如果當前SubList的modCount和ArrayList的modCount不一樣也會拋出例外,
checkForComodification();
//這里呼叫的是ArrayList的add(int,object)傳入的第一個引數是1+2=3也就是在ArrayList集合的第三個下標處添加元素,
//也就是說添加的元素始終是在SubList的最后一個,最終的結果ABCFDE
parent.add(parentOffset + index, e);
//將ArrayList的modCount賦值給SubList的modCount
this.modCount = parent.modCount;
this.size++;
}
//獲取元素
public E get(int index) {
rangeCheck(index);
checkForComodification();
//要獲取的下標+偏移量
return ArrayList.this.elementData(offset + index);
} }}
為什么說增強for就是迭代器?
最后再回到forEach這個方法,SubList中本身是沒有這個方法的,它來自于Iterable繼承體系是
SubList->AbstractList->AbstractCollection->Collection->Iterable
這是一個默認實作的方法,這里看似很簡單,無非就是回圈當前物件,然后把當前元素值傳遞
給Consumer.accept(o)但是這么想就錯了,因為當前這個this其實是SubList但是SubList里并沒有
Object[]也就是它根本就沒有裝在元素的陣列,
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
斷點后的截圖如下

走到這我就好奇了,這個this到底是怎么生成的,于是我想使用反編譯工具JD-JUI看看,但是失敗了,這個工具如果有拉姆達運算式就會失敗,
于是我打算曲線救國,既然這個forEach使用的增強for那么我就看看增強for這個語法糖到底干了什么?


不得不噴一句這個工具太垃圾了!于是我又找到了JDK自帶的工具javap

終于這就是我要的答案!它走的是迭代器!!!SubList也有自己的迭代器,這里只看next就能看出來,每次查詢下一個其實就是使用了offset偏移量+index就像是get方法一樣,到此結束!不過有空我還要去找一個好點
的反編譯器徹底的反編譯一把這個代碼,

Arrays.asList
//這個方法不是ArrayList中的方法它是Arrays工具包里的方法,
//之所以聊到它是因為這個方法接收可變長度的陣列然后回傳
//一個ArrayList但是這個ArrayList可不是咱們本文的ArrayList,
//而是Arrays工具類中的內部類,
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}

看一個有意思的代碼片段,

這段代碼執行是會報錯的,原因就是雖然回傳的也是ArrayList但是這個ArrayList只是繼承了AbstractList而AbstractList類中的add方法本身
的add方法是這個樣子的,
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/235911.html
標籤:其他

