ArrayList删除方法解析

本文主要解析ArrayList的删除方法,分析了多种删除方式,如for-each循环、迭代器遍历、普通遍历删除等,指出不同方式的运行结果及存在的问题,还介绍了父类中remove的实现逻辑,最后总结在单线程环境下删除List元素的要点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ArrayList删除方法解析

多种删除方式分析:

首先上一段代码:

public static void main(String[] args) {
      List<String> list = new ArrayList<>();
      list.add("a");
      list.add("b");
      list.add("c");
      System.out.println("删除前的元素:"+ list);
      System.out.println("==============================");
      
      for (String s : list) {
         if ("c".equals(s)) {
            list.remove(s);
         }
      }
      
      System.out.println("删除后的元素:"+ list);
      System.out.println("删除成功!");
   }

这个运行结果是:

删除前的元素:[a, b, c]

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:907)
at java.util.ArrayList$Itr.next(ArrayList.java:857)
at com.example.other.test.other.ListRemoveTest.main(ListRemoveTest.java:27)

我们来分析下原因。

首先反编译:

public static void main(String[] args) {
    List<String> list = new ArrayList();
    list.add("a");
    list.add("b");
    list.add("c");
    System.out.println("删除前的元素:" + list);
    System.out.println("==============================");
    Iterator var2 = list.iterator();

    while(var2.hasNext()) {
        String s = (String)var2.next();
        if ("c".equals(s)) {
            list.remove(s);
        }
    }

    System.out.println("删除后的元素:" + list);
    System.out.println("删除成功!");
}

可以看出for-each循环其实是使用的迭代器循环,下面看下ArrayList相关的源码

  • 1.首先是list.remove(s)
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
  • 1.2关键代码fastRemove(index):
/*
 * Private remove method that skips bounds checking and does not
 * return the value removed.
 */
private void fastRemove(int index) {
    modCount++;
    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
}

解释一下关键字段的含义:

modCount:每一次对list的增删,该值都会加一

  • 2.看这部分源码 String s = (String)var2.next();

找到ArrayList中的内部类Itr,它是Iterator的实现类,找到下面的方法

public E next() {
    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];
}
  • 2.2关键方法checkForComodification();
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

关键字段expectedModCount

  • 2.3在这一步时Iterator var2 = list.iterator();,对Itr初始化,然后将modCount赋值给expectedModCount
public Iterator<E> iterator() {
    return new Itr();
}

当1.1处remove时,1.2处使modCount+1,而expectedModCount的值没有变化,所以在2.2处会抛出ConcurrentModificationException异常。

如何解决这个问题呢?

一个简单的办法,就是在remove后,break掉,不要让它执行下一次循环。

public static void main(String[] args) {
   List<String> list = new ArrayList<>();
   list.add("a");
   list.add("b");
   list.add("c");
   System.out.println("删除前的元素:"+ list);
   System.out.println("==============================");
   
   for (String s : list) {
      if ("c".equals(s)) {
         list.remove(s);
         break;
      }
   }
   
   System.out.println("删除后的元素:"+ list);
   System.out.println("删除成功!");
}

这个运行结果是可以正常删除。

删除前的元素:[a, b, c]

删除后的元素:[a, b]
删除成功!

这种办法只能做一次删除,局限性很大。

有另一种更好的解决办法,直接迭代器遍历:

public static void main(String[] args) {
   List<String> list = new ArrayList<>();
   list.add("a");
   list.add("b");
   list.add("c");
   System.out.println("删除前的元素:"+ list);
   System.out.println("==============================");
   
   Iterator<String> i = list.iterator();
   while (i.hasNext()){
      if ("a".equals(i.next())) {
         i.remove();
      }
   }
   
   System.out.println("删除后的元素:"+ list);
   System.out.println("删除成功!");
}

运行结果:

删除前的元素:[a, b, c]

删除后的元素:[b, c]
删除成功!

还是看源码,分析为什么它可以删除成功:

关键方法i.remove();

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    // 检查是否list是否被修改
    checkForComodification();

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

删除方法ArrayList.this.remove(lastRet);

public E remove(int 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;
}

这一步没什么,删除指定索引的元素,同时modCount+1

重要的一步:

expectedModCount = modCount;

把修改后的modeCount又赋值给expectedModCount 了,所以下一次的循环检查不会抛出异常。

还有一种是普通的遍历删除,如下:

public static void main(String[] args) {
   List<String> list = new ArrayList<>();
   list.add("a");
   list.add("b");
   list.add("c");
   System.out.println("删除前的元素:"+ list);
   System.out.println("==============================");
   
   for (int i = 0, length = list.size(); i < length; i++) {
      if ("b".equals(list.get(i))) {
         list.remove(i);
      }
   }
   
   System.out.println("删除后的元素:"+ list);
   System.out.println("删除成功!");
}

运行结果:

删除前的元素:[a, b, c]

Exception in thread “main” java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.get(ArrayList.java:433)
at com.example.other.test.other.ListRemoveTest.main(ListRemoveTest.java:28)

原因是什么呢?

当遍历到b时,i=1,length=3,list.size=3,执行删除后list.size=2,下一次循环i=2,数组长度为2是的最大下表为1,显然已经越界。

还有另一种写法:

public static void main(String[] args) {
   List<String> list = new ArrayList<>();
   list.add("a");
   list.add("b");
   list.add("c");
   System.out.println("删除前的元素:"+ list);
   System.out.println("==============================");
   
   for (int i = 0; i < list.size(); i++) {
      if ("b".equals(list.get(i))) {
         list.remove(i);
      }
   }
   
   System.out.println("删除后的元素:"+ list);
   System.out.println("删除成功!");
}

运行结果:

删除前的元素:[a, b, c]

删除后的元素:[a, c]
删除成功!

删除成功,但是这种方式会让循环提早结束。

可见普通的循环删除是多么的不靠谱,不要使用这种方式。

父类中remove的实现:

说完各种删除方式后,我们再来看看ArrayList父类AbstractList中remove是怎样的逻辑。

AbstractList.remove

下面这个方法是根据索引删除元素:

public E remove(int index) {
    throw new UnsupportedOperationException();
}

由此可见,如果一个AbstractList的子类没有重写remove,就会抛出UnsupportedOperationException异常。比如Arrays.asList(T… a)方法,由它转化的ArrayList是Arrays内部类,它没有重写remove和add,如果调用这两个方法会抛出如上异常。

AbstractCollection.remove

下面这个方法是AbstractList的父类AbstractCollection的remove方法,

根据元素删除元素:

public boolean remove(Object o) {
    Iterator<E> it = iterator();
    if (o==null) {
        while (it.hasNext()) {
            if (it.next()==null) {
                it.remove();
                return true;
            }
        }
    } else {
        while (it.hasNext()) {
            if (o.equals(it.next())) {
                it.remove();
                return true;
            }
        }
    }
    return false;
}

第一步: Iterator it = iterator();

其子类AbstractList有实现,调用子类:

public Iterator<E> iterator() {
    return new Itr();
}

it.hasNext(),it.next()的在AbstractList中都有实现,和ArrayList相仿,就不赘述了,各位自行研究。

第二步:重点说明这个方法的实现it.remove();

首先Iterator中的默认实现,

default void remove() {
    throw new UnsupportedOperationException("remove");
}

如果其实现类中没有该方法的实现,则抛出如上异常。

显然AbstractList中的Itr对Iterator有了改方法的实现,如下:

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    // 检查list有无被修改
    checkForComodification();

    try {
        AbstractList.this.remove(lastRet);
        if (lastRet < cursor)
            cursor--;
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException e) {
        throw new ConcurrentModificationException();
    }
}

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

关键是这一句:

AbstractList.this.remove(lastRet);

如果AbstractList的子类对remove方法没有重写,则抛出UnsupportedOperationException异常。

总结:

综上,如果要删除List中的元素,如果是一个三方包装的ArrayList,首先要确定有没有重写remove方法,然后再考虑使用迭代器的方式,当然前提是在单线程环境中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值