前言
点赞在看,养成习惯。
点赞收藏,人生辉煌。
点击关注【微信搜索公众号:编程背锅侠】,防止迷路。
ArrayList系列文章
第一篇:ArrayList中的构造方法源码在面试中被问到了...抱歉没准备好!!!告辞
第二篇:面试官让我讲ArrayList中add、addAll方法的源码...我下次再来
第三篇:工作两年还没看过ArrayList中remove、removeAll、clear方法源码的都来报道吧
第四篇: 乱披风锤法锤炼ArrayList源码中的get、set、contains、isEmpty方法!!
第五篇:满屏飘红,操作ArrayList的Iterator方法时竟然给我报ConcurrentModificationException异常
删除方法表格
| 方法名 | 描述 |
|---|---|
| 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()执行成功了,这个留给各位看官自己去想,哈哈哈😂。
创作不易, 非常欢迎大家的点赞、评论和关注(^_−)☆
你的点赞、评论以及关注是对我最大的支持和鼓励,而你的支持和鼓励
我继续创作高质量博客的动力 !!!