



这两天review了一下java.util包下集合相关类的源码,温故而知新每次阅读都会有不同的理解与收获,在前几篇文章中简单介绍了数据结构相关操作以及给出了相应的Java实现,这样再学习Java的集合自然就得心应手。
Java集合概述
1、面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储,Java集合就像一种容器,可以动态的存储多个对象的引用。
2、Java集合分为Collection和Map两种体系,其中Collection用于存储数量不等的多个对象;Map用于存储数量不等的具有key-value映射关系的内容。
3、Collection接口继承关系:

① List:元素有序,可重复的集合 (动态数组)
② Set:元素无序、不可重复的集合 (类似高中的集合)
③ Queue:队列
4、Map接口常用实现类:

6、Collection接口中定义了一些基本操作:

1.存储对象时可以考虑使用:①数组 ②集合
2.数组存储对象的特点:
弊端:
① Student[] stu = new Student[20]; stu[0] = new Student();一旦创建,其长度不可变,使用不方便,如果有必要的话,可以使用边长数组来避之;
②真实的数组存放的对象的个数是不可知,所以可能造成空间浪费,也可能空间不足;
优点:
① 处理基本类型数据,数组性能高;
eg:对一个数据集求和:
/*** 求数组datas中所有元素和* @param datas* @return*/public static int sumByArray(int[] datas){int sum = 0;for (int i = 0; i < datas.length; i++) {sum += datas[i];}return sum;}/*** 求集合datas中所有元素之和* @param datas* @return*/public static int sumByList(List<Integer> datas){int sum = 0;for (int i = 0; i < datas.size(); i++) {sum += datas.get(i);}return sum;}
这里集合求和时:sum+=datas.get(i)其实已经做了一个拆箱动作,Integer对象通过intValue方法自动转换成了一个int基本类型,对于性能濒于临界的系统来说该方案是比较危险的,特别是大数量的时候,首先,在初始化List数组时要进行装箱动作,把一个int类型包装成一个Integer对象,虽然有整型池在,但不在整型池范围内的都会产生一个新的Integer对象,而且众所周知,基本类型是在栈内存中操作的,而对象则是在堆内存中操作的,栈内存的特点是速度快,容量小,堆内存的特点是速度慢,容量大(从性能上来讲,基本类型的处理占优势)。其次,在进行求和计算(或者其他遍历计算)时要做拆箱动作,无疑产生性能消耗。所以对基本类型进行求和计算时,性能要求较高的场景中使用数组替代集合。
① Java 集合就像一种容器,可以动态地把多个对象的引用放入容器中;
② Java 集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组;
4、浅拷贝问题:
① 什么是浅拷贝和深拷贝?
浅拷贝:将原对象或原数组的引用直接赋给新对象、新数组;
深拷贝:创建一个新的对象和数组,将原对象的各项属性的值(数组的所有元素)拷贝过来,是值而不是引用;
②数组的Arrays.copyOf方法产生的数组是一个浅拷贝,基本类型是直接拷贝值,其他都是拷贝引用地址;
③ 数组的clone、集合的clone方法也是浅拷贝。
④浅拷贝存在的问题就是:如果拷贝的是引用,拷贝后的对象如果发生变化会影响原对象,原对象会发生同样的变化。
4、使用Arrays.asList方法踩过的坑:
①数组转集合:Arrays.asList,不知大家是否在使用的时候踩过坑,举个栗子:
public static void main(String[] args) {int[] datas = {1,2,3,4};List list = Arrays.asList(datas);System.out.println("list的大小:"+list.size());}
可能有人会说,这还不简单吗,当然是4了,然而事实并非如此,打印结果是:list的大小:1,惊不惊讶,为什么会是这样呢?明明datas数组就是有4个元素啊,别急,我们看源码找答案:
public static <T> List<T> asList(T... a) {return new ArrayList<>(a);}
可以看到asList方法接受的是一个泛型变长参数,我们知道基本类型是不能泛型化的,也就是说8个基本类型不能作为泛型参数,要想作为泛型参数就必须使用其所对应的包装类型。那前面的例子传递了一个int类型的数组,为什么程序没有报编译错呢?
在Java中,数组是一个对象,它是可以泛型化的,也就是说我们的例子是把一个int类型的数组作为了T的类型,所以转换后在List中就只有一个类型为int数组的元素了,我们打印出来看看,代码如下:
public static void main(String[] args) {int[] datas = {1,2,3,4};List list = Arrays.asList(datas);System.out.println("list的大小:"+list.size());System.out.println(list.get(0).getClass());}打印结果:list的大小:1class [I
咦,不是说元素是数组类型吗,为什么打印的class是:“[I”而不是Array类型呢呀?这是因为JVM不可能输出Array类型,因为Array是属于java.lang.reflect包的,它是通过反射访问数组元素的工具类。在Java中任何一个数组的类都是“[I”,究其原因就是Java并没有定义数组这一个类,它是在编译器编译的时候生成的,是一个特殊的类,在JDK的帮助中也没有任何数组类的信息。
弄清楚了问题,我们只需将数组定义为包装类型即可,代码如下:
public static void main(String[] args) {Integer[] datas = {1,2,3,4};List list = Arrays.asList(datas);System.out.println("list的大小:"+list.size());}
这时输出的结果就是4,所以一定要注意,在使用asList方法的时候,参数不能是基本类型数组(8种),否则会出错。
public static void main(String[] args) {Integer[] datas = {1,2,3,4};List list = Arrays.asList(datas);System.out.println("list为"+list);list.add(5);System.out.println("添加新元素后的list为"+list.size());}
大家觉得能添加成功吗?不妙添加的时候编译不报了,一运行结果如下:
list为[1, 2, 3, 4]Exception in thread "main" java.lang.UnsupportedOperationExceptionat java.util.AbstractList.add(AbstractList.java:148)at java.util.AbstractList.add(AbstractList.java:108)at test.com.knowledge.TestCollection.main(TestCollection.java:96)
又是坑啊,返回List类型,执行add方法竟然报不支持的操作,奇怪了,我们还是继续追踪源码:
public static <T> List<T> asList(T... a) {return new ArrayList<>(a);}
直接new了一个ArrayList为什么会不支持add方法呢,不应该呀,然后点开ArrayList一看,奥,这里的ArrayList不是java.util.ArrayList,而是Arrays的一个静态私有内部类,只有Arrays能访问:
private static class ArrayList<E> extends AbstractList<E>implements RandomAccess, java.io.Serializable{private static final long serialVersionUID = -2764017481108945198L;//存储列表元素的数组private final E[] a;//唯一的构造方法ArrayList(E[] array) {a = Objects.requireNonNull(array);}@Overridepublic int size() {return a.length;}//转化为数组,实现数组的浅拷贝@Overridepublic Object[] toArray() {return a.clone();}@Override@SuppressWarnings("unchecked")public <T> T[] toArray(T[] a) {int size = size();if (a.length < size)return Arrays.copyOf(this.a, size,(Class<? extends T[]>) a.getClass());System.arraycopy(this.a, 0, a, 0, size);if (a.length > size)a[size] = null;return a;}//获取指定元素@Overridepublic E get(int index) {return a[index];}@Overridepublic E set(int index, E element) {E oldValue = a[index];a[index] = element;return oldValue;}@Overridepublic int indexOf(Object o) {E[] a = this.a;if (o == null) {for (int i = 0; i < a.length; i++)if (a[i] == null)return i;} else {for (int i = 0; i < a.length; i++)if (o.equals(a[i]))return i;}return -1;}//判断是否包含某元素@Overridepublic boolean contains(Object o) {return indexOf(o) != -1;}@Overridepublic Spliterator<E> spliterator() {return Spliterators.spliterator(a, Spliterator.ORDERED);}@Overridepublic void forEach(Consumer<? super E> action) {Objects.requireNonNull(action);for (E e : a) {action.accept(e);}}@Overridepublic void replaceAll(UnaryOperator<E> operator) {Objects.requireNonNull(operator);E[] a = this.a;for (int i = 0; i < a.length; i++) {a[i] = operator.apply(a[i]);}}@Overridepublic void sort(Comparator<? super E> c) {Arrays.sort(a, c);}}
在该类中没有定义add方法,在父类AbstractList中确实找到了add方法:
public boolean add(E e) {add(size(), e);return true;}public void add(int index, E element) {throw new UnsupportedOperationException();}
但是需要子类提供相应的实现,而这里内部类ArrayList并没有实现,所以会抛出异常;
我们看到内部类ArrayList中对于我们经常使用的List.add和List.remove方法它都没有实现,也就是说asList返回的是一个长度不可变的列表,数组是多长,转换成的列表也就是多长,换句话说此处的列表只是数组的一个外壳,不再保持列表动态变长的特性,这才是我们要关注的重点,所以我们切记不能使用asList来初始化一个列表。
1、List存储的元素有序可重复:这里判断元素是否重复是通过添加元素的equals方法判断的,所以添加到List中的元素需要重写equals方法;
2、集合中的每个元素都有其对应的顺序索引,容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
3、List接口常用的实现类:
① ArrayList:底层是数组,数组查询特定元素比较快,而插入和删除和修改比较慢,数组在内存中是一块连续的内存,如果插入或删除是需要移动内存;
② LinkedList:底层是双向链表,链表不要求内存是连续的,在当前元素中存放下一个或上一个元素的地址,查询时需要从头部开始,一个一个的找,所以查询效率低,但是插入删除时不需要移动内存,只需改变引用指向即可,所以插入或者删除的效率高。
③Vector:是一个古老的集合,JDK1.0就有了,大多数操作与ArrayList相同,区别之处在于Vector是线程安全的,效率低;
注:这里数组和链表是如何实现增删改查操作的,这里就不再赘述了,前面有篇文章在介绍数据结构线性表的时候介绍过。
④ List的equals方法:判断列表相等,只关心元素是否相等:
public static void main(String[] args) {Integer[] datas = {1,2,3,4};ArrayList<String > arrayList = new ArrayList<String>(2);arrayList.add("AA");Vector<String> vector = new Vector<String>(2);vector.add("AA");System.out.println(arrayList.equals(vector));}结果:true
很奇怪一个是ArrayList,一个是Vector,结果竟然相等,我们看看equals的源码,在AbstractList中定义:
public boolean equals(Object o) {if (o == this)return true;//这里只要实现了List接口即可if (!(o instanceof List))return false;//迭代访问两个List的元素ListIterator<E> e1 = listIterator();ListIterator<?> e2 = ((List<?>) o).listIterator();while (e1.hasNext() && e2.hasNext()) {E o1 = e1.next();Object o2 = e2.next();//只要存在不相等的就退出if (!(o1==null ? o2==null : o1.equals(o2)))return false;}//长度是否也相等return !(e1.hasNext() || e2.hasNext());}
所以可以看到,只要是实现了List接口即可,它不关心List的具体实现类,只要所以的元素相等并且长度也相等就说明两个List相等。其他集合eg:Set、Map等也是如此,判断两个集合是否相等,只关心集合元素是否相等不关心集合类型。
和String类的subString有点类似,但功能是否相同呢?我们看个例子:
public static void main(String[] args) {List<String > list = new ArrayList<String>(2);list.add("AA");list.add("BB");//通过集合构造方法创建List1List<String > list1 = new ArrayList<String>(list);//通过subList构建List<String> list2 = list.subList(0, list.size());list2.add("CC");System.out.println("list == list1 ?:"+list.equals(list1));System.out.println("list == list2 ?:"+list.equals(list2));}
大家想一想结果是什么呢?先别急着回答我们先来看看String的subString是什么情况?
public static void main(String[] args) {String str = "AB";String str1 = new String (str);String str2 = str.substring(0) + "C";System.out.println("str == str1 ?:"+str.equals(str1));System.out.println("str == str2 ?:"+str.equals(str2));}结果:str == str1 ?:truestr == str2 ?:false
结果很明显,我们也不难理解,因为String类的equals是判断字符串的内容是否相等,所以str2和str1不相等,而str和str1即使不是同一个对象但是值相同,所以结果为true。
说完subString,我们再来看subList是否也是如此呢?我们看运行结果如下:
list == list1 ?:falselist == list2 ?:true
和你预期结果一直吗?这里同样是sub操作,但结果却相反,知其然知其所以然,所以我们来看看subList的源码:
public List<E> subList(int fromIndex, int toIndex) {return (this instanceof RandomAccess ?new RandomAccessSubList<>(this, fromIndex, toIndex) :new SubList<>(this, fromIndex, toIndex));}class RandomAccessSubList<E> extends SubList<E> implements RandomAccess {RandomAccessSubList(AbstractList<E> list, int fromIndex, int toIndex) {super(list, fromIndex, toIndex);}
subList方法是由AbstractList实现的,它会根据是不是可以随机存取来提供不同的SubList实现方式,不过,随机存储的使用频率比较高,而且RandomAccessSubList也是SubList子类,所以所有的操作都是由SubList类实现的,那么我们就直接看SubList的代码:
class SubList<E> extends AbstractList<E> {//原始列表private final AbstractList<E> l;//子列表相对于原始列表的偏移量private final int offset;//子列表的大小private int size;//这里的list就是我们的原始列表SubList(AbstractList<E> list, int fromIndex, int toIndex) {//校验下标if (fromIndex < 0)throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);if (toIndex > list.size())throw new IndexOutOfBoundsException("toIndex = " + toIndex);if (fromIndex > toIndex)throw new IllegalArgumentException("fromIndex(" + fromIndex +") > toIndex(" + toIndex + ")");l = list;offset = fromIndex;size = toIndex - fromIndex;this.modCount = l.modCount;}public E set(int index, E element) {rangeCheck(index);checkForComodification();return l.set(index+offset, element);}//获取指定位置元素public E get(int index) {rangeCheck(index);//每次操作前先校验子列表和原始列表中的修改次数变量是否一致checkForComodification();//从原始列表中返回指定位置的元素,实质是对原始列表操作return l.get(index+offset);}public int size() {checkForComodification();return size;}public void add(int index, E element) {rangeCheckForAdd(index);checkForComodification();l.add(index+offset, element);this.modCount = l.modCount;size++;}public E remove(int index) {rangeCheck(index);checkForComodification();E result = l.remove(index+offset);this.modCount = l.modCount;size--;return result;}protected void removeRange(int fromIndex, int toIndex) {checkForComodification();l.removeRange(fromIndex+offset, toIndex+offset);this.modCount = l.modCount;size -= (toIndex-fromIndex);}public boolean addAll(Collection<? extends E> c) {return addAll(size, c);}public boolean addAll(int index, Collection<? extends E> c) {rangeCheckForAdd(index);int cSize = c.size();if (cSize==0)return false;checkForComodification();l.addAll(offset+index, c);this.modCount = l.modCount;size += cSize;return true;}public Iterator<E> iterator() {return listIterator();}public ListIterator<E> listIterator(final int index) {checkForComodification();rangeCheckForAdd(index);return new ListIterator<E>() {private final ListIterator<E> i = l.listIterator(index+offset);public boolean hasNext() {return nextIndex() < size;}public E next() {if (hasNext())return i.next();elsethrow new NoSuchElementException();}public boolean hasPrevious() {return previousIndex() >= 0;}public E previous() {if (hasPrevious())return i.previous();elsethrow new NoSuchElementException();}public int nextIndex() {return i.nextIndex() - offset;}public int previousIndex() {return i.previousIndex() - offset;}public void remove() {i.remove();SubList.this.modCount = l.modCount;size--;}public void set(E e) {i.set(e);}public void add(E e) {i.add(e);SubList.this.modCount = l.modCount;size++;}};}public List<E> subList(int fromIndex, int toIndex) {return new SubList<>(this, fromIndex, toIndex);}private void rangeCheck(int index) {if (index < 0 || index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}private void rangeCheckForAdd(int index) {if (index < 0 || index > size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}private String outOfBoundsMsg(int index) {return "Index: "+index+", Size: "+size;}private void checkForComodification() {if (this.modCount != l.modCount)throw new ConcurrentModificationException();}}
通过阅读源码,我们就非常清楚subList方法的实现原理了:它返回的SubList类也是AbstractList的子类,其所有的方法如get、set、add、remove等都是在原始列表上的操作,它自身并没有生成一个数组或是链表,也就是子列表只是原列表的一个视图(View),所有的修改动作都反映在了原列表上。
我们例子中的list2增加了一个元素C,不过增加的元素C到了list列表上,两个变量的元素仍保持完全一致,相等也就很自然了。解释完相等的问题,再回过头来看看为什么变量list与list1不相等,很简单,因为通过ArrayList构造函数创建的List对象list1实际上是新列表,它是通过数组的copyOf动作生成的,所生成的列表list1与原列表list之间没有任何关系,虽然是浅拷贝,但元素类型是String,也就是说元素是深拷贝的,然后list又增加了元素,所有list与list1之间已经没有一毛钱的关系了,所以就不相等。
所以subList产生的列表只是一个视图,所有的修改动作直接作用于原列表。
接下来我们再来看一个例子:
@Testpublic void testSubList(){List<String > list = new ArrayList<String>();list.add("AA");list.add("BB");list.add("CC");list.add("DD");list.add("EE");List<String> subList = list.subList(1,3);list.add("DD");System.out.println(list.size());System.out.println(subList.size());}
大家想一想,运行结果是什么呢?如果生成子列表以后我们又修改了原列表,那视图是否会跟着变呢?我们看上面的代码,list.add("DD")会报错吗?不会,因为subList并没有锁定原列表,所以原列表也可以修改,继续往下看,难道会有两个size方法吗?没错就是size方法报错了:
6java.util.ConcurrentModificationExceptionat java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231)at java.util.ArrayList$SubList.listIterator(ArrayList.java:1091)at java.util.AbstractList.listIterator(AbstractList.java:299)at java.util.ArrayList$SubList.iterator(ArrayList.java:1087)at java.util.AbstractCollection.toString(AbstractCollection.java:454)at java.lang.String.valueOf(String.java:2994)at java.io.PrintStream.println(PrintStream.java:821)at test.com.knowledge.TestCollection.testSubList(TestCollection.java:28)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675)at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125)at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:139)at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:131)at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:81)at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104)at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62)at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43)at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35)at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:202)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)at java.util.ArrayList.forEach(ArrayList.java:1249)at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)at java.util.ArrayList.forEach(ArrayList.java:1249)at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:74)at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
subList的size报并发修改异常,没有多线程操作为什么会报并发修改异常呢?其实也不难理解,subList的列表是原列表的一个视图,原数据集(修改了,但是subList取出的子列表不会重新生成一个新列表,后面在对子列表继续操作时,就会检测到修改计数器与预期的不相同,于是就抛出了并发修改异常。
上面我们从SubList的size放的源码也可以看到,在操作之前会先调用方法checkForComodification检测是否并发修改,如果发现subList的修改器和原列表的不一致就会异常。
我们可以使用Collections.unmodifiableList(list),将原列表设为只读状态,所有生成了子列表后就要保持原列表为只读状态。
如果大家发现有什么不妥之处或分析不到位的地方,非常欢迎留言讨论交流哦










