一家之言 姑妄言之 絮絮叨叨 不足爲訓
筆者廢話:
這篇文章是ArrayList源碼逐條解析外述篇。爲什麼來個外述篇呢?因爲:
1. 這個類作爲ArrayList
的迭代方式是非常重要的;
2. 我是實在不想在“ArrayList的遍歷功能解析”中解析這個類了,本身它是非常重要的,如果不單拿出來講而是放在ArrayList源碼逐條解析這個文章裏解析其實會給人造成誤解認爲其不重要;
3. 公司裏的領導告訴我源碼分析寫的過於長可能影響觀感。
所以,我這裏把這個類單拿出來進行解析(>ω<)。
ArrayList-Itr類註釋翻譯:
一個優化版的AbstractList.Itr
筆者廢話:
這個註釋其實已經告訴了我們,這個類是一個優化類,是我們ArrayList
所繼承的AbstractList
抽象類中Itr
類的一個優化版本。我們平常在使用ArrayList
的iterator()
方法時,其底層用的就是這個類。
ArrayList-Itr成員變量信息:
int cursor; // 下一個要返回的元素的索引
int lastRet = -1; // 最後一個返回元素的索引;如果沒有就是-1
int expectedModCount = modCount;
我們來看ArrayList-Itr的成員變量信息。
首先,cursor
代表了下一個要返回的元素的索引,這裏源碼的單行註釋也已經說的很通透了。其實cursor
就是一個遊標指向,指向的就是我們所將要遍歷數組中的下一項。譬如我們有一個數組:[“a” , “b” , “c” , “d”]。
當我們初始化拿到Iterator
的時候,這個cursor
就已經指向了第一項,也就是說cursor = 0
,代表,你好,我接下來要開始指向"a"了。
其次,lastRet
代表了最後一個返回元素的索引,如果沒有就是-1。這裏源碼的單行註釋也已經說的很通透了。其實lastRet
也是一個指向,指向的就是我們所將要遍歷數組中的上一項。我們還拿上面例子的數組舉例。
當我們初始化拿到Iterator
的時候,這個cursor
就已經指向了第一項,也就是說cursor = 0
,那麼lastRet
指向哪裏呢?答,指向上一項。也就是元素"a"的左側,也就是"沒有",那麼"沒有"是多少呢?看註釋,-1。
所以這裏我們就可以進行推算了,當cursor
已經指向了第二項,也就是指向了"b",這個時候cursor = 1
,而lastRet
指向上一項,也就是lastRet = 0
。每一次lastRet
都會比cursor
少1,cursor
表示當前元素,lastRet
則表示上一個元素。
最後,我們來看expectedModCount
。從字面翻譯上來看叫做“期望修改值”,那麼這個“期望修改值”是誰呢。代碼中告訴我們這個值就是我們ArrayList
中的操作值modCount
。其實這個modCount
都是繼承自AbstractList
抽象類中的,他們是互通的。注意:這個屬性很重要,這裏會引出ConcurrentModificationException這個異常的觸發場景。
筆者廢話:
一般人們喜歡將cursor
其翻譯成“指針”,我是覺得這種翻譯…可能這種翻譯更加的直白,但是我還是喜歡把它翻譯成“遊標”。因爲我們的Java當初宣稱是程序員不需要操作指針,而且Java也屏蔽(封裝在底層)指針,如果這裏依舊用“指針”這個字眼我個人認爲不是很合適。當然,我特別喜歡“遊標”這個詞。因爲這讓我想起了我最初用的遊標卡尺。彷彿將這個遊標卡尺比喻成我們ArrayList
所代表的底層數組再也合適不過了。
ArrayList-Itr構造函數信息:
Itr() {}
這裏是Itr
的構造函數,從形式上來看是一個無參構造函數。這裏顯式聲明無參構造函數的目的是爲了Itr
的子類ListItr
在進行構造時能搜索到父類的構造函數,這個從ListItr
的有參構造函數就可能觀察得到。另外,這個無參構造函數也在被ArrayList
的iterator()
所調用,具體就不解釋了,都在ArrayList源碼逐條解析裏了。
ArrayList-Itr的方法解析:
好,我們現在開始進入正題了,別忘了,這個Itr
類就是對接口Iterator
的具體實現。具體的情形可先閱讀Iterator源碼逐條解析倒數第二段。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
這裏我們先來介紹一個方法checkForComodification()
——檢測修改值方法。我
們先不關切這個方法被誰來調用,我們就單純的看它在幹什麼。
首先,我們看到這個方法被final
關鍵字修飾了,這代表如果這個類被繼承的話,這個方法是不可以被重寫的。
其次,這裏面的代碼體也非常簡單,它在判斷,我們當前ArrayList
的操作值modCount
是否不等於Irt
的期望修改值expectedModCount
。一旦發現不相等,就拋出ConcurrentModificationException
異常。
OK,這裏,我們就把本文“ArrayList-Itr成員變量信息”中所介紹的ConcurrentModificationException異常觸發場景的坑填了。
也就是說,一旦我發現你當前的修改值並不等於我當初的修改值,那麼,你完了,你已經違反了我們ArrayList
的規定:對當前數組內的內容進行了修改,無論是單線程還是多線程。這個時候是必然拋出ConcurrentModificationException
異常的。
它就是判斷程序對當前數組遍歷過程中是否有非法操作行爲進行合理化判斷,避免內部元素更改造成業務邏輯混亂(也對,你遍歷就遍歷你瞎改什麼…)。
public boolean hasNext() {
return cursor != size;
}
這裏覆寫了Iterator
接口中的hasNext()
。我們知道這個方法是在詢問當前遍歷的容器中是否含有下一個元素,那麼我們看具體的實現是如何呢?
cursor != size
,對,就是這樣。它在判斷我們的cursor
遊標是否不等於當前數組內元素的個數size
。因爲如果等於了size
不就代表我們這個遊標指向已經指向了最後一個元素的後一位了嗎?
這裏要記住,我們的遊標是從0
這個位置開始的,所以即使遍歷到了最後一個元素它的值也是size-1
。只有執行了next()
方法之後,利用next()
方法將元素取出後,這個cursor
纔會+1(也就是遊標指向向右移動),達到size
這個值的水平。這時也能得出我們已經遍歷了最後一個元素,不會有下一個元素了。所以我們說,這裏的實現方式非常的精妙,利用一個遊標就定位出是否含有下一個元素。
千萬記住,這裏的hasNext()
方法調用完後,遊標指向cursor
是不會移動的。
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];
}
這裏是迭代的重要步驟,這個next()纔是真正可以取出元素的方法。我們經過了之前的hasNext()
方法判斷是否含有下一個元素之後,就可以緊接着調用該方法。
我們來看這個方法的具體實現。
第一步,先利用checkForComodification()
方法對修改值modCount
進行檢查,判斷當前數組是否發生修改。
第二步,聲明一個局部變量i
,並將當前遊標值cursor
賦予它(其實這裏就是遊標本身,我們接下來描述的時候就用“遊標”這個概念)。
第三步,判斷遊標是否大於或等於當前數組元素個數size
,如果符合判斷條件,則拋出NoSuchElementException
異常。這點還是易於理解的,之前我們說過,當這個遊標cursor
的值等於了數組元素個數size
就已經表示沒有剩餘的集合進行遍歷了,何況還要大於size
。那麼這個時候拋出異常就不足爲奇了。
第四步,是將當前ArrayList
的數組賦予一個新的類型爲Object
的數組,其實就是把當前的數組複製了一份;
第五步,到了這裏,你看,又一次把遊標進行判斷,判斷什麼呢?判斷我們的遊標是否大於或等於了數組的容量length
,如果符合判斷條件,則拋出ConcurrentModificationException
異常。看,我們這裏又有一個熟悉的異常,那麼爲什麼遊標大於或等於了數組的容量length
就會拋出這個異常呢?其實,它還是在做檢測,檢測集合是否被修改過。
筆者廢話:
到這裏你仔細想想,我們假設元素個數size
就是數組長度length
,也就是說這個數組已經被填滿了。但是這個時候,你通過了第三步的元素個數判斷,但是沒有通過第五步的容量判斷,也就是發生了這種場景:length <= i < size
。這種情況可能嗎?數組容量小於了數組內的元素個數?這種情況有可能,就是你刪除了元素,然後你的數組縮容了。說的直白一些當你remove()
完畢後你又調用了類似於trimToSize()
的縮容方法。這可不就是修改了集合元素嘛~所以,這個時候拋出ConcurrentModificationException
異常是一種非常正確的行爲。
第六步,這個時候,該判斷的也都判斷了,我們可以正常的取出元素返回給調用者了。不過這裏我們需要開始最重要的一步了,挪動我們的遊標卡尺。形如代碼所說的那樣,當前遊標i + 1
即可。也就是這個遊標指向向右移動一格。
第七步,返回所遍歷的元素,返回哪個呢?當然返回我們之前想要遍歷的那個遊標所在處的元素,也就是那個i
,也就是返回數組當前i
索引處的元素。不過,返回之前我們還要明白,既然我們的遊標cursor
已經向右挪動了一格,那麼是不是我們的上一項遊標lastRet
也要向右挪動一格呢?它肯定是緊跟cursor
的,所以,這個lastRet
值就是當前的i
值。不用懷疑,因爲你的下一項遊標cursor
已經加1了。這個cursor
的左側lastRet
勢必就是當初的那個小i
,再細緻一點的話就是cursor = i + 1
,lastRet = cursor - 1 = i + 1 - 1 = i
。
到此,next()
方法解析完成~
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();
}
}
接下來我們介紹一個移除方法——remove()
。
這個方法主要是刪除利用next()
方法返回的元素。那麼這裏爲什麼另起一個移除方法,而不是用ArrayList
本身的remove()
方法呢?我們往下看…
我們這裏先不介紹代碼的具體步驟,先舉出最核心的移除步驟,也就是ArrayList.this.remove(lastRet)
。我們看,其實整個移除方法利用的還是ArrayList
內部的remove()
方法。當然,這裏不是重點,重點是那個索引值——lastRet
。記住這個值,這裏刪除的是當前遊標所在位置左移一格的元素(當前一項的前一項)。根據上述next()
方法解析來看,當我們元素獲取之後,遊標cursor
右移,同時上一項遊標lastRet
也進行右移。所以,這個時候lastRet
指向的就是我們之前next()
方法遍歷出的那個元素。這也就解釋了爲什麼我們一般使用這個remove()
方法的時候是跟在next()
方法之後的(因爲你想刪除的那個元素就是lastRet
指向的那個元素)。
所以這個時候,我們纔開始代碼的解析步驟:
第一步,判斷lastRet
是否小於0
。如果小於了0
,則拋出IllegalStateException
異常。
我們之前說過,當我們初始化拿到Iterator
的時候,這個cursor
就已經指向了第一項,也就是說cursor = 0
,而lastRet
是在起始位置的左側,也就是"沒有",也就是-1。那麼我們上面說了,調用remove()
方法刪除的就是lastRet
指向的那個元素,然而這個的索引值是-1
,我們能刪除一個不存在的元素嗎?顯然是不能的。只有在調用一次next()
方法之後遊標右移,纔可以正常的刪除元素。所以,這裏需要進行一次判斷,來規避錯誤刪除的場景。
第二步,利用checkForComodification()
方法對修改值modCount
進行檢查,判斷當前數組是否發生修改。這一步屬於通用操作,就不須解釋了。
第三步,構建try-catch
代碼塊,一旦捕獲到異常就拋出ConcurrentModificationException
異常(其實這裏還是圍繞操作值modCount
和預期修改值exceptModCount
展開描述的)。
第四步,極爲重要的一步——移除元素。雖說上面已經提到這個方法了,我這裏還是提一下。這一步會調用ArrayList
內部的remove(int index)
方法,注意,這裏的remove(int index)
方法可是會修改操作值modCount
的。
第五步,將當前的lastRet
賦予當前的cursor
。這一步其實就是我們之前在ArrayList源碼逐條解析中講到的remove(int index)
填坑操作。當我們把當前的lastRet
所指向的元素刪除時,這裏其實就已經有了空缺,但是放心,remove(lastRet)
操作通過複製數組已經把這個元素的後一項元素直到最後一項元素往前移動了。所以,我們之前通過next()
操作移動的遊標cursor
其實已經指向了其本身元素的後一項。因此,這裏就需要把遊標cursor
前移一格,也就是往左移,怎麼移動呢?不就是lastRet
所代表的那個位置嗎?也就是說這裏會有一步cursor = lastRet
的操作讓遊標cursor
指向正確的地方。
可能這種表述不太直觀,這裏還是舉個例子:譬如我們有一個數組:
a | b | c | d | e |
---|---|---|---|---|
0 | 1 | 2 | 3 | 4 |
當我們遍歷到"b"的時候,cursor = 2
,lastRet = 1
。這個時候我們把"b"刪除,這個數組就變成了:
a | c | d | e |
---|---|---|---|
0 | 1 | 2 | 3 |
那麼這個時候,如果是cursor = 2
,lastRet = 1
就肯定不正確了。cursor
指向了"d"而跳過了"c",我們需要把這個遊標歸正回來,所以就使用cursor = lastRet
讓cursor = 1
以使cursor
指向"c"。這樣就清楚多了。
第六步,將lastRet
初始化爲最初的-1
。其實這一步我並不明白爲什麼會初始化爲-1
,我想着它可以做這種操作:lastRet = lastRet - 1
。也就是這個最後一個返回元素的索引向左移動一格。不過,這種初始化-1的操作也不影響程序的正常運行,因爲在next()
方法中,這個lastRet
值依舊會被賦予正確的指向。
第七步,同步操作值modCount
到exceptModCount
,這個就不須過多的解釋了,因爲remove(lastRet)
操作會使得當前ArrayList
的操作值modCount
加1,所以這裏爲了避免ConcurrentModificationException
異常你需要把這個修改了的值更新到我們Itr
中的期望修改值exceptModCount
內。
到此,remove()
方法解析完成~
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// 在迭代結束時更新一次,以減少堆寫流量
cursor = i;
lastRet = i - 1;
checkForComodification();
}
我們接下來介紹的這個方法forEachRemaining(Consumer<? super E> consumer)
從方法名來看是在進行遍歷操作,遍歷什麼呢?遍歷剩餘元素。
筆者廢話:
其實這個方法我並沒有使用過,但是我還是嘗試的做了一些小實驗,發現這個方法還是挺可愛的。
這個方法的目的是爲了遍歷每個剩餘元素,然後執行給定的操作,直到所有的元素都已經被處理或拋出一個異常。那麼從何而來的這些剩餘元素呢?當然是我們之前遍歷過後剩餘下來的元素。不過,這裏需要仔細想一下,什麼時候會出現遍歷集合還剩餘元素的場景呢?
OK,我們這裏先打住,這些使用場景我會在後面描述,我們還是先進入主題,進入源碼分析部分。
開始分析:
第一步,我們先看方法參數。這個參數是一個Consumer<? super E>
類型的參數。我們仔細跟蹤它的話會發現這是一個函數式編程接口,關於這個接口的信息已經在Consumer源碼逐條解析中講解到了,這裏就不贅述了。總的來說,從這一步我們就明白了,這個遍歷方法是要進行業務處理的。也就是我們之前說過的“執行給定的操作”。
第二步,判斷consumer
參數是否爲空。確實是,我們需要判斷一下給定操作是否存在,不存在的話也沒必要往下執行了。
第三步,獲取當前ArrayList
的元素個數size
並將其賦予新聲明的變量size
。值得一提的是,這裏新聲明的size
是final
修飾的。代表着這個屬性在當前調用的情況下是不可變的。爲什麼這裏是不可變得呢?事實上,final
修飾的變量並非指的是“值”不可變,而是“引用”不可變。我們試想一種情況,假如我們當前的ArrayList
內的元素個數發生了改變,我們還需要用原先的元素個數size
進行遍歷嗎?顯示不行,所以,這裏需要“引用不可變”,一直指向這個ArrayList.this.size
。
第四步,我們需要獲取當前剩餘元素的遊標值cursor
並賦予i
,這一步是必須的。
第五步,對這個遊標值i
進行判斷,如果大於或等於了元素個數size
就返回不執行了。這裏大於的情況就不說了,我們着重說一下這個等於的情況。
在第八步的時候我們會真正的對這個集合開始遍歷,你會發現取用元素的時候這個遊標i
會進行i++
操作。記住這一步,我們一直說數組索引是從0開始的,所以這裏遍歷完所有元素後最後的那個索引應該是size - 1
。然而這裏爲了方便遊標i
向右移動,所以增添了i++
操作。這種操作是無可厚非的,但是關鍵在於最後一個元素遍歷後,這個遊標i
依舊會自動加1。所以這裏我們可以理解爲此時自增後的i
值不僅僅表示了索引,還表示了元素個數。一旦發生i == size
的情況也就表明了我們整個數組已經遍歷完畢,不需要進行再次遍歷了。所以,這裏就進行了i >= size
判斷。一旦符合條件,就返回不執行了。
第六步,獲取當前ArrayList
的數組並賦予新聲明的elementData
,同時,這個新聲明的elementData
也是被final
修飾的。這也表明了我們此時操作的數組引用是不可變的。
第七步,對我們當前的遊標i
進行判斷,看其是否大於或等於了數組的容量。如果符合判斷條件,則拋出ConcurrentModificationException
異常。這個判斷解析已經在上面的next()
方法解析中已經講解過了,如果忘了就跳到上面next()
方法解析的“筆者廢話”中再看一遍。
第八步,開始真正的遍歷集合,並執行給定的操作。這裏的判斷依據是我們的遊標i
並不等於元素個數size
。也就是說這裏要判斷我們的元素並沒有真正的遍歷完。然後同時需要滿足的條件是當前ArrayList
的操作值modCount
是符合預期修改值expectedModCount
的,也就是遍歷期間是不允許修改集合的。
通過判斷之後,就可以取出元素,然後利用消費者consumer
來執行給定的操作。注意,這裏取出元素的索引用的就是我們當前的遊標i
,只不過操作完成後會執行i++
操作以便移動遊標位置(索引位置)。
第九步,這裏就是更新現在遊標cursor
的操作。將我們現在經過自增的i
賦予cursor
,理論上來看,這個時候的cursor
也代表着我們元素的個數size
(因爲我們最後執行的是size - 1 + 1
)。
第十步,更新lastRet
,這個lastRet
的計算結果就比較符合我們之前的那個remove()
方法中第六步所提到的計算方式,即,lastRet = i - 1
。也就是說此時的lastRet
絕對是緊跟在cursor
左側的。而此時的lastRet
也指向了我們數組中的最後一個元素。
最後,依舊利用checkForComodification()
方法對修改值modCount
進行檢查,判斷當前數組是否發生修改。
至此,forEachRemaining(Consumer<? super E> consumer)
方法我們就解析完畢了。
不過到這裏我們還是對這個方法有些說明的,也是爲了填上面什麼時候會出現遍歷集合還剩餘元素的場景的坑。
那麼我們可以仔細想一下,這個方法的應用場景是什麼時候呢?
其實它的使用場景有兩個:
1. 遍歷時發生異常,還存有未遍歷的元素;
2. 單純需要用遍歷處理業務。
針對於第一條來說,當遍歷出現異常的時會進入這個遍歷的場景。注意:這裏說的遍歷是利用iterator()
方法,你用for循環出現了異常這個forEachRemaining(Consumer<? super E> consumer)
是不起作用的。
譬如這個例子:
/**
* 這個是錯誤示例啊,不過放心運行
*/
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
arrayList.add("d");
Iterator<String> iterator = arrayList.iterator();
try {
for (String a : arrayList) {
if ("a".equals(a)) throw new RuntimeException();
}
} catch (Exception e) {
iterator.forEachRemaining(wangcai -> {
System.out.println("遍歷出的剩餘元素: " + wangcai);
});
}
}
/*
* 輸出結果:
* 遍歷出的剩餘元素: a
* 遍歷出的剩餘元素: b
* 遍歷出的剩餘元素: c
* 遍歷出的剩餘元素: d
*/
你看這個例子,我們利用for循環拋出了異常,再用forEachRemaining(Consumer<? super E> consumer)
方法進行遍歷其實是沒變化的。哪有什麼剩餘元素,全剩下來了。所以,這種操作是錯誤的。我們來舉兩個正確使用該方法的例子:
/**
* 這個是正確的示例,放心運行
*/
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
arrayList.add("d");
Iterator<String> iterator = arrayList.iterator();
try {
iterator.forEachRemaining(s -> {
if (iterator.next().equals("a")) throw new RuntimeException();
});
} catch (Exception e) {
iterator.forEachRemaining(laifu -> {
System.out.println("遍歷出的剩餘元素: " + laifu);
});
}
}
/*
* 輸出結果:
* 遍歷出的剩餘元素: b
* 遍歷出的剩餘元素: c
* 遍歷出的剩餘元素: d
*/
/**
* 這個是正確的示例,放心運行
*/
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
arrayList.add("d");
Iterator<String> iterator = arrayList.iterator();
try {
while (iterator.hasNext()) {
if ("a".equals(iterator.next())) throw new RuntimeException();
}
} catch (Exception e) {
iterator.forEachRemaining(laifu -> {
System.out.println("遍歷出的剩餘元素: " + laifu);
});
}
}
/*
* 輸出結果:
* 遍歷出的剩餘元素: b
* 遍歷出的剩餘元素: c
* 遍歷出的剩餘元素: d
*/
你看,我們這裏又舉了兩個例子來證明這個方法的使用場景。就是一旦發生異常,我還可以遍歷剩下的元素。無論你是用傳統的while
式方法拋出異常,還是利用forEachRemaining(Consumer<? super E> consumer)
方法本身拋出異常,我們都能接住然後進行後續的處理。
比如我們平時推送數據,需要遍歷集合推送集合內的數據到其他平臺,如果遍歷發生異常難道我們就不管了嗎?不能吧?所以可以利用這種方式進行保底推送。也就是說,我最少可以規避一次錯誤,然後將剩餘數據推送過去。當然,你第二次推送再發生問題的話,記得報告你的項目經理,商量一下扣工資的事兒,可能你的項目經理會把這個事兒攔下了然後不用扣了的說→_→
針對於第二條來說,你會發現,這其實就是一個遍歷方法。即使是我們分析的底層源碼也是一個while
循環遍歷。唯獨的區別就是我們可以執行給定的操作。而且方便的是,因爲函數式編程接口的引用,你還可以使用lambda
表達式。毫不避諱的說,夠咱們裝一壺得了(*•̀ᴗ•́*)و …
所以這麼說來,我們是可以利用它做業務處理的(當然第一條也是做了業務處理,這裏涉及的可能是重要的業務處理,不過我不推薦這個場景的做法。畢竟,這個方法的本意是要遍歷剩餘元素)。來,我們舉個例子:
/**
* 處理大量業務的使用場景,不過我不太建議這麼做,即使咱們說實話這樣沒什麼錯
*/
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
arrayList.add("d");
Iterator<String> iterator = arrayList.iterator();
iterator.forEachRemaining(laifu -> {
System.out.println("————————————————————");
System.out.println("你看我能不能推送: " + laifu);
System.out.println("你看我能不能結賬: " + laifu);
System.out.println("你看我能不能停單: " + laifu);
System.out.println("你看我能不能吃飯: " + laifu);
System.out.println("————————————————————");
});
}
/*
* 輸出結果:
* ————————————————————
* 你看我能不能推送: a
* 你看我能不能結賬: a
* 你看我能不能停單: a
* 你看我能不能吃飯: a
* ————————————————————
* ————————————————————
* 你看我能不能推送: b
* 你看我能不能結賬: b
* 你看我能不能停單: b
* 你看我能不能吃飯: b
* ————————————————————
* ————————————————————
* 你看我能不能推送: c
* 你看我能不能結賬: c
* 你看我能不能停單: c
* 你看我能不能吃飯: c
* ————————————————————
* ————————————————————
* 你看我能不能推送: d
* 你看我能不能結賬: d
* 你看我能不能停單: d
* 你看我能不能吃飯: d
* ————————————————————
*/
這個例子不太長就是輸出結果長點兒,不過長點兒好,這也能說明這個forEachRemaining(Consumer<? super E> consumer)
方法是不是也可以在正常情況下遍歷元素,然後進行大量的業務邏輯處理?對,毋庸置疑,是的。所以…嗯,兩個場景介紹完了,坑填平~
至此,我們ArrayList-Itr
到此全部解析完畢(ಥ_ಥ)。