暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Java集合List的这些操作你都知道吗?

ITSK 2019-11-03
469
风雨之后不一定总能见彩虹,但咬紧嘴唇温柔又倔强的奋斗之后定会让你悄无声息遇到一个更优秀的自己,致奔跑在路上的小伙伴们,加油





这两天review了一下java.util包下集合相关类的源码,温故而知新每次阅读都会有不同的理解与收获,在前几篇文章中简单介绍了数据结构相关操作以及给出了相应的Java实现,这样再学习Java的集合自然就得心应手。


Java集合概述
1、面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储,Java集合就像一种容器,可以动态的存储多个对象的引用。
2、Java集合分为Collection和Map两种体系,其中Collection用于存储数量不等的多个对象;Map用于存储数量不等的具有key-value映射关系的内容。
3、Collection接口继承关系:

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

5、在Java5之前,Java集合会丢失容器中所有对象的数据类型,把所有对象都当成Object类型处理,从Java5增加了泛型以后,Java集合可以记住容器中对象的数据类型。
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对象,而且众所周知,基本类型是在栈内存中操作的,而对象则是在堆内存中操作的栈内存的特点是速度快,容量小,堆内存的特点是速度慢,容量大(从性能上来讲,基本类型的处理占优势)。其次,在进行求和计算(或者其他遍历计算)时要做拆箱动作,无疑产生性能消耗。所以对基本类型进行求和计算时,性能要求较高的场景中使用数组替代集合。

3、集合存储对象的特点:
① 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的大小:1
class [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种),否则会出错。

② asList方法产生的List对象不可更改,你知道吗:
 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.UnsupportedOperationException
at 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);
}


@Override
public int size() {
return a.length;
}


        //转化为数组,实现数组的浅拷贝
@Override
public 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;
}


        //获取指定元素
@Override
public E get(int index) {
return a[index];
}


@Override
public E set(int index, E element) {
E oldValue = a[index];
a[index] = element;
return oldValue;
}


@Override
public 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;
}


//判断是否包含某元素
@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}


@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(a, Spliterator.ORDERED);
}


@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
for (E e : a) {
action.accept(e);
}
}


@Override
public 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]);
}
}


@Override
public 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来初始化一个列表。


List接口:
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等也是如此,判断两个集合是否相等,只关心集合元素是否相等不关心集合类型。

⑤ List的subList方法:返回一个列表的子列表,实质是原列表的一个视图:
和String类的subString有点类似,但功能是否相同呢?我们看个例子:
public static void main(String[] args) {
List<String > list = new ArrayList<String>(2);
list.add("AA");
list.add("BB");


//通过集合构造方法创建List1
List<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 ?:true
str == str2 ?:false

结果很明显,我们也不难理解,因为String类的equals是判断字符串的内容是否相等,所以str2和str1不相等,而str和str1即使不是同一个对象但是值相同,所以结果为true。

说完subString,我们再来看subList是否也是如此呢?我们看运行结果如下:

list == list1 ?:false
list == 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();
else
throw new NoSuchElementException();
}


public boolean hasPrevious() {
return previousIndex() >= 0;
}


public E previous() {
if (hasPrevious())
return i.previous();
else
throw 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产生的列表只是一个视图,所有的修改动作直接作用于原列表。

接下来我们再来看一个例子:

  @Test
public 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方法报错了:

6


java.util.ConcurrentModificationException
at 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),将原列表设为只读状态,所有生成了子列表后就要保持原列表为只读状态。


如果大家发现有什么不妥之处或分析不到位的地方,非常欢迎留言讨论交流哦




欢迎关注ITSK,每天进步一点点,我们追求在交流中收获成长和快乐



文章转载自ITSK,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论