前言
點贊在看,養成習慣。
點贊收藏,人生輝煌。
點擊關注【微信搜索公衆號:編程背鍋俠】,防止迷路。
ArrayList系列文章
第一篇:ArrayList中的構造方法源碼在面試中被問到了…抱歉沒準備好!!!告辭
第二篇:面試官讓我講ArrayList中add、addAll方法的源碼…我下次再來
第三篇:工作兩年還沒看過ArrayList中remove、removeAll、clear方法源碼的都來報道吧
第四篇: 亂披風錘法錘鍊ArrayList源碼中的get、set、contains、isEmpty方法!!!肝起來
第五篇: 滿屏飄紅,操作ArrayList的Iterator方法時竟然給我報ConcurrentModificationException異常,擼ta
源碼方法表格
方法名 | 描述 |
---|---|
public String toString() | 把集合所有數據轉換成字符串 |
public Iterator iterator() | 普通迭代器 |
public int indexOf(Object o) | 將集合清空 |
public int lastIndexOf(Object o) | 刪除與給定集合中相同的元素 |
default void remove() | ArrayList內部的迭代器中的remove方法,刪除集合中的元素 |
java.util.ConcurrentModificationException | 迭代器中ConcurrentModificationException異常 |
public String toString()把集合所有數據轉換成字符串
案例演示
@Test
public void test_toString(){
ArrayList<String> list = new ArrayList<>();
list.add("洛洛01");
list.add("洛洛02");
// 把集合所有數據轉換成字符串
String x = list.toString();
System.out.println(x);
}
源碼分析
// 把集合所有數據轉換成字符串
public String toString() {
// 注意:此時相當於用ArrayList對象在調用iterator()方法獲取迭代器,那麼這個時候需要先看看ArrayList中的iterator()方法,ArrayList自己實現了List中的Iterator方法
Iterator<E> it = iterator();
// 調用ArrayList中hasNext方法判斷是否有元素,如果hasNext()方法返回false
// 那麼就toString方法就返回一個 "[]"
if (! it.hasNext())
return "[]";
// 創建StringBuilder,對集合的內容進行拼接,避免字符串頻繁拼接產生很多無效對象
StringBuilder sb = new StringBuilder();
// 拼接字符串的左側中括號
sb.append('[');
// 無限循環遍歷
for (;;) {
// 調用ArrayList中next方法取出元素
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
// 調用ArrayList中hasNext方法判斷是否有元素,如果hasNext()方法返回false
// 那麼就拼接一個 "]"並轉換爲toString返回
if (! it.hasNext())
return sb.append(']').toString();
// 拼接元素分隔符【, 】逗號、空格
sb.append(',').append(' ');
}
}
// ArrayList中的Iterator方法,ArrayList集合內部類
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// 返回ArrayList集合內部Iterator類的對象
public Iterator<E> iterator() {
return new Itr();
}
// ArrayList內部實現的Iterator類
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;
Itr() {}
// 判斷光標和集合的大小 是否不相等,不相等證明有下一個元素
public boolean hasNext() {
return cursor != size;
}
// 獲取下一個元素,下方的源碼分析會講解
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
// 併發修改異常
final void checkForComodification() {
// 如果預期修改次數 和 實際修改次數不相等 就產生併發修改異常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}
總結
返回此集合的字符串表示形式。如果集合爲空,則返回
"[]"
。如果集合中有元素,則返回"[元素, 元素]"
以
','
和空格分隔的字符串列表。
public Iterator iterator()普通迭代器
案例演示
@Test
public void test_toString_ie(){
ArrayList<String> list = new ArrayList<>();
list.add("洛洛01");
list.add("洛洛02");
list.add("洛洛03");
// 獲取迭代器
Iterator<String> iterator = list.iterator();
// 遍歷集合
while (iterator.hasNext()){
String next = iterator.next();
System.out.println(next);
}
}
源碼分析
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// ArrayList內部類
// 一定要注意觀察 Itr 類中的幾個成員變量
public Iterator<E> iterator() {
return new Itr();
}
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
//將實際修改集合次數 賦值 給預期修改次數
//在迭代的過程中,只要實際修改次數和預期修改次數不一致就會產生併發修改異常
//由於expectedModCount是Itr的成員變量,那麼只會被賦值一次!!!
//同時由於集合調用了三次add方法,那麼實際修改集合次數就是 3,因此expectedModCount的值也是 3
int expectedModCount = modCount;
Itr() {}
// 判斷是否有下一個元素
public boolean hasNext() {
return cursor != size;
}
// 獲取下一個元素
@SuppressWarnings("unchecked")
public E next() {
// 校驗修改的次數和預期的次數是否相等,不相等拋出ConcurrentModificationException異常
checkForComodification();
// 把下一個元素的索引賦值給i
int i = cursor;
// 如果這個索引大於集合的長度,拋出NoSuchElementException異常
if (i >= size)
throw new NoSuchElementException();
// 將集合底層存儲數據的數組賦值給迭代器的局部變量 elementData
Object[] elementData = ArrayList.this.elementData;
// 再次判斷,如果下一個元素的索引大於集合底層存儲元素的長度 併發修改異常
// 注意,儘管會產生併發修改異常,但是這裏顯示不是我們要的結果
if (i >= elementData.length)
throw new ConcurrentModificationException();
// 每次成功獲取到元素,下一個元素的索引都是當前索引+1
cursor = i + 1;
// 返回元素
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
// 如果預期修改次數 和 實際修改次數不相等 就產生併發修改異常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}
總結
使用迭代器循環遍歷這個集合,這個迭代器是
ArrayList
源碼裏面的迭代器。hasNext
方法是用來判斷是否有下一個元素存在,next
方法是獲取元素。
modCount
修改的次數和expectedModCount
預期的次數需要關注一下。
public int indexOf(Object o)查詢給定元素在集合中首次出現的索引
案例演示
@Test
public void test_indexOf(){
ArrayList<String> list = new ArrayList<>();
list.add("洛洛01");
list.add("洛洛02");
list.add("洛洛02");
list.add("洛洛03");
int i = list.indexOf("洛洛02");
System.out.println(i);// 1
}
源碼分析
// 根據指定的值o,獲取這個值在集合中的索引
public int indexOf(Object o) {
// 判斷這值是否爲null
if (o == null) {
// 循環遍歷這個集合
for (int i = 0; i < size; i++)
// 找到集合中第一個值爲null,並返回這個值的索引
if (elementData[i]==null)
return i;
} else {
// 給定的值o不爲null,循環遍歷這個集合
for (int i = 0; i < size; i++)
// 判斷集合中是否有給定的相等元素,如果有就返回這個值的索引
if (o.equals(elementData[i]))
return i;
}
// 集合中不存在這個給定的值o,返回-1
return -1;
}
總結
根據指定的值o,獲取這個值在集合中的索引。判斷這個集合中是否存在給定的元素,如果沒有返回-1;如果有就返回第一次查找到這個元素的索引。
public int lastIndexOf(Object o)查詢給定好元素在集合中最後一次出現的索引
案例演示
@Test
public void test_lastIndexOf(){
ArrayList<String> list = new ArrayList<>();
list.add("洛洛01");
list.add("洛洛03");
list.add("洛洛02");
list.add("洛洛03");
list.add("洛洛03");
// 獲取集合中與"洛洛03"相等的元素,倒序遍歷
int i = list.lastIndexOf("洛洛03");
System.out.println(i);// 4
}
源碼分析
// // 根據指定的值o,獲取這個值在集合中的索引
public int lastIndexOf(Object o) {
// 判斷這值是否爲null
if (o == null) {
// 給定的值爲null,循環遍歷這個集合,倒序遍歷的
for (int i = size-1; i >= 0; i--)
// 判斷是否有null,有了返回這個索引i
if (elementData[i]==null)
return i;
} else {
// 給定的值不爲null,倒序遍歷集合
for (int i = size-1; i >= 0; i--)
// // 判斷集合中是否有給定的相等元素,如果有就返回這個值的索引,從後往前找這個相等值
if (o.equals(elementData[i]))
return i;
}
// 集合中沒有給定的值,返回-1
return -1;
}
總結
根據指定的值o,獲取這個值在集合中的索引。判斷這個集合中是否存在給定的元素,如果沒有返回-1;如果有就返回最後一次查找到這個元素的索引。源碼中的循環是倒序遍歷。
default void remove()迭代器中的remove方法,刪除集合中的元素
代碼演示
@Test
public void test_iterator_remove(){
ArrayList<String> list = new ArrayList<>();
list.add("洛洛01");
list.add("洛洛02");
list.add("洛洛02");
list.add("洛洛03");
// 這一坨代碼將在下面使用removeIf方法代替,jdk的lambda表達式代替
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String next = iterator.next();
if (next.equals("洛洛02")){
iterator.remove();
}
}
list.forEach(System.out::println);
}
// 【removeIf】方法的使用是不是很爽可以簡化很多代碼
@Test
public void test_iterator_remove(){
ArrayList<String> list = new ArrayList<>();
list.add("洛洛01");
list.add("洛洛02");
list.add("洛洛02");
list.add("洛洛03");
list.removeIf(next -> next.equals("洛洛02"));
list.forEach(System.out::println);
}
源碼分析
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// ArrayList內部類
// 一定要注意觀察 Itr 類中的幾個成員變量
public Iterator<E> iterator() {
return new Itr();
}
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
// 將實際修改集合次數賦值給預期修改次數
// 在迭代的過程中,只要實際修改次數和預期修改次數不一致就會產生併發修改異常
// 由於expectedModCount是Itr的成員變量,那麼只會被賦值一次!!!
// 同時由於集合調用了三次add方法,那麼實際修改集合次數就是 3,因此expectedModCount的值也是 4
int expectedModCount = modCount;
// 沒有參數的構造方法
Itr() {}
// 【重點這個方法】迭代器刪除元素方法
public void remove() {
// 判斷最後返回元素的索引是否小於0,滿足條件就產生【非法狀態異常】
if (lastRet < 0)
throw new IllegalStateException();
// 校驗是否會產生併發修改異常,第一次調用不會,因爲與其修改次數和實際修改次數一致
checkForComodification();
try {
// 真正刪除集合元素的方法,【調用方法爲ArrayList的方法remove】,且將0作爲參數進行傳遞
// 這個方法的源碼分析在ArrayList系列文章的另一篇裏面可以去看一下
ArrayList.this.remove(lastRet);
// 將lastRet賦值給cursor
cursor = lastRet;
// 再次等於-1
lastRet = -1;
// 再次將集合實際修改次數賦值給預期修改次數,那麼這個時候不管集合自身是否刪除成功
// 那麼實際修改次數和預期修改次數又一致了,所以並不會產生併發修改異常
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
}
迭代器中的java.util.ConcurrentModificationException異常的產生
案例演示
@Test
public void test_toString_CME_01(){
List<String> list = new ArrayList<>();
list.add("洛洛01");
list.add("洛洛02");
list.add("洛洛03");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String next = iterator.next();
if (next.equals("洛洛03")){
// 這一行代碼會引起ConcurrentModificationException異常
// 爲什麼這一行代碼會引起ConcurrentModificationException異常呢?
// 這其中發生了什麼?
list.remove("洛洛03");
}
}
System.out.println(list.toString());
}
// 針對以上會出現ConcurrentModificationException異常,可以使用iterator自己的remove方法避免這種異常
@Test
public void test_toString_CME_01(){
List<String> list = new ArrayList<>();
list.add("洛洛01");
list.add("洛洛02");
list.add("洛洛03");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String next = iterator.next();
if (next.equals("洛洛03")){
// 這個方法可以解決上面的異常
iterator.remove();
}
}
System.out.println(list.toString());
}
產生異常的源碼
final void checkForComodification() {
// 集合修改的次數,不等於期望修改的次數expectedModCount拋出ConcurrentModificationException異常
if (modCount != expectedModCount)
// 異常就是從這個地方來的
throw new ConcurrentModificationException();
}
異常提示
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
異常分析
查看修改的次數modCount的值和期望修改的值expectedModCount,這兩個值的變化
總結
針對上面的案例以及兩張圖,
debug
測試會發現iterator.next()
方法報錯了,其實是checkForComodification
方法報的錯。針對以上的案例演示,報異常的原因
expectedModCount
爲3。這個值其實來源於ArrayList源碼
中的內部類Itr
,他的值在調用list.iterator()
方法的時候已經給定。但是
modCount
我們一共修改了4次,其中3次add
和1次remove
操作。就是最後一次的這個
list.remove
操作導致的這個異常。這個方法是ArrayList
的方法。操作這個的modCount
方法不會同步給Itr
中的expectedModCount
。所以這兩個值不相等,就報了這個異常。可以使用Itr
中自己的remove
方法。異常出現的位置是
String next = iterator.next();
這一行代碼。其實還有一個點就是爲什麼iterator.hasNext()執行成功了,這個留給各位看官自己去想,哈哈哈😂。
創作不易, 非常歡迎大家的點贊、評論和關注(^_−)☆
你的點贊、評論以及關注是對我最大的支持和鼓勵,而你的支持和鼓勵
我繼續創作高質量博客的動力 !!!