ArrayList踩过的那些坑

Arrays

首先我们看看Arrays类。Arrays类提供了将数据转为集合的方法asList()

Arrays.asList()

List<Integer> arrayList = Arrays.asList(0, 0, 4, 2, 5, 0, 3, 0);
arrayList.add(10);
System.out.println(arrayList);

执行add操作后会出现异常?

我们去看看Arrays的源码

public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

根据这个方法的官方注释我们可以知道Arrays.asList返回的是大小固定的列表。

而且这里的ArrayList并不是java.util包下的ArrayList而是Arrays的一个内部类。

private static class ArrayList<E> extends AbstractList<E>
    implements RandomAccess, java.io.Serializable

这个ArrayList和util包下的ArrayList一样都集成了AbstractList

但是不同的是这个List并没有重写AbstractList中的add和remove方法

AbstractList中的add方法

public boolean add(E e) {
    add(size(), e);
    return true;
}
public void add(int index, E element) {
    throw new UnsupportedOperationException();
}

这样,上述提出的问题就能得到解释。

再来看看ArrayList的remove操作

public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>(Arrays.asList(0, 0, 4, 2, 5, 0, 3, 0));
        remove(arrayList,0);
        System.out.println(arrayList);

    }
    public static void remove(ArrayList<Integer> list, Integer elem) {
        // 方法一:普通for循环正序删除,删除过程中元素向左移动,不能删除重复元素(具有不确定性)
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).equals(elem)) {
                list.remove(i);
            }
        }
        // 方法二:普通for循环倒序删除,删除过程中元素向左移动,可以删除重复的元素
        for (int i = list.size() - 1; i >= 0; i--) {
            if (list.get(i).equals(elem)) {
                list.remove(i);
            }
        }
        // 方法三:增强for循环删除,使用ArrayList的remove()方法删除,产生并发修改异常 ConcurrentModificationException
        for (Integer str : list) {
            if (str.equals(elem)) {
                list.remove(str);
            }
        }
        // 方法四:迭代器,使用ArrayList的remove()方法删除,产生并发修改异常 ConcurrentModificationException
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            if(iterator.next().equals(elem)) {
                list.remove(iterator.next());
            }
        }

        // 方法五:迭代器,使用迭代器的remove()方法删除,可以删除重复的元素
        Iterator iterator2 = list.iterator();
        while (iterator2.hasNext()) {
            if(iterator2.next().equals(elem)) {
                iterator2.remove();
            }
        }
    }

}

方法三和方法四都抛出了ConcurrentModificationException这个异常,我们要了解这个异常首先要了解modCount。这个变量第一在AbstractList中

protected transient int modCount = 0;

根据注释可以看出他记录的是List结构修改(add、remove)的次数。这是变量在iterator中会用到。

ArrayList的add方法

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

在执行elementData[size++]=e之前首先会执行ensureCapacityInternal(size+1)方法

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
    }

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

这个方法是在判断是否需要扩容(ArrayList是基于动态数组实现的)可以看到在判断的时候对modCount++。

所以每当我们给list添加一个元素时就会执行一次++操作,根据我们的代码此时modCount=8数据的大小size=8

再看看Iterator的具体实现类Itr(ArrayList的内部类)

private class Itr implements Iterator<E> {
    int cursor;       // 下一个要返回元素的索引
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;
    //是否有下一个元素取决于cursor不等于size最开始我们的cursor=0
//size=8
    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
	//当expectedModCount != modCount;会抛出
//ConcurrentModificationException异常
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;
        checkForComodification();
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

当我们得到Iterator的具体对象时cursor=0,expectedModCount = modCount=8

hasNext()当cousor!=size的时候就证明有下一个元素

next()会先判断expectedModCount 和modCount是否相等

ArrayList的remove方法有两个先看看List如何定义?

/**
 * Removes the element at the specified position in this list (optional
 * operation).  Shifts any subsequent elements to the left (subtracts one
 * from their indices).  Returns the element that was removed from the
 * list.
 *
 * @param index the index of the element to be removed
 * @return the element previously at the specified position
 * @throws UnsupportedOperationException if the <tt>remove</tt> operation
 *         is not supported by this list
 * @throws IndexOutOfBoundsException if the index is out of range
 *         (<tt>index &lt; 0 || index &gt;= size()</tt>)
 */
E remove(int index);

删除此列表中指定位置的元素(可选操作)。将任何后续元素向左移动(从其索引中减去一个)。返回从列表中删除的元素。

/**
 * Removes the first occurrence of the specified element from this list,
 * if it is present (optional operation).  If this list does not contain
 * the element, it is unchanged.  More formally, removes the element with
 * the lowest index <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
 * (if such an element exists).  Returns <tt>true</tt> if this list
 * contained the specified element (or equivalently, if this list changed
 * as a result of the call).
 *
 * @param o element to be removed from this list, if present
 * @return <tt>true</tt> if this list contained the specified element
 * @throws ClassCastException if the type of the specified element
 *         is incompatible with this list
 * (<a href="Collection.html#optional-restrictions">optional</a>)
 * @throws NullPointerException if the specified element is null and this
 *         list does not permit null elements
 * (<a href="Collection.html#optional-restrictions">optional</a>)
 * @throws UnsupportedOperationException if the <tt>remove</tt> operation
 *         is not supported by this list
 */
boolean remove(Object o);

从该列表中删除第一个出现的指定元素(可选操作)。如果此列表不包含元素,则它是不变的。更正式地说,删除索引最低的元素

再看看ArrayList中的具体实现

/**
 * Removes the element at the specified position in this list.
 * Shifts any subsequent elements to the left (subtracts one from their
 * indices).
 *
 * @param index the index of the element to be removed
 * @return the element that was removed from the list
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E remove(int index) {
	//判断index是否合法
    rangeCheck(index);

    modCount++;
	//获得当前元素
    E oldValue = elementData(index);
    //将此索引后面得元素往前移动
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
	//将最后一个位置置为空
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}
/**
 * Removes the first occurrence of the specified element from this list,
 * if it is present.  If the list does not contain the element, it is
 * unchanged.  More formally, removes the element with the lowest index
 * <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
 * (if such an element exists).  Returns <tt>true</tt> if this list
 * contained the specified element (or equivalently, if this list
 * changed as a result of the call).
 *
 * @param o element to be removed from this list, if present
 * @return <tt>true</tt> if this list contained the specified element
 */
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
	//将o与集合中的元素进行比较,如果相等移除
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

可以看到不管是哪个remove方法都对modCount进行++操作

remove(Object o)是在fastRemove()方法中进行了++

所以当我们使用了list.remove方法后modCount和expectedModCount就会不相等。

避免此异常的方法

  1. 使用iterator遍历时,需要修改集合则使用iteratoer自己的remove方法(因为在iteratoer中会有expectedModCount = modCount;操作)
  2. 使用for、foreach循环,使用list的remove方法(但是可能会有所误差)
  3. 记录要删除元素的下表,最后一次删除

最后ArrayList的两个remove方法你能区别吗?

public static void main(String[] args) {
    List<Integer> arrayList = new ArrayList<>(Arrays.asList(0, 0, 4, 2, 5, 0, 3, 0));
    Integer k = 0;
    while (k < arrayList.size()) {
        if (arrayList.get(k) == 0) {
            arrayList.remove(k);
        }
        k++;
    }
    System.out.println(arrayList);
}

输出结果为[0, 2, 0, 3, 0]

public static void main(String[] args) {
    List<Integer> arrayList=new ArrayList<>(Arrays.asList(0,0,4,2,5,0,3,0));
    int k=0;
    while(k<arrayList.size()){
        if(arrayList.get(k)==0){
            arrayList.remove(k);
            System.out.println(k+" "+arrayList);
        }
        k++;
    }
    System.out.println(arrayList);
}

输出结果为[0, 4, 2, 5, 3]

参考博客:

https://blog.csdn.net/Jiangshan11/article/details/83038857

https://www.jianshu.com/p/a7df6082ec00

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值