滿屏飄紅,操作ArrayList的Iterator方法時竟然給我報ConcurrentModificationException異常,擼ta源碼

前言

點贊在看,養成習慣。

點贊收藏,人生輝煌。

點擊關注【微信搜索公衆號:編程背鍋俠】,防止迷路。

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()執行成功了,這個留給各位看官自己去想,哈哈哈😂。

創作不易, 非常歡迎大家的點贊、評論和關注(^_−)☆
你的點贊、評論以及關注是對我最大的支持和鼓勵,而你的支持和鼓勵
我繼續創作高質量博客的動力 !!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章