ArrayList删除方法解析

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方法,然后再考虑使用迭代器的方式,当然前提是在单线程环境中。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ArrayListJava集合框架中的一个类,它实现了List接口,可以用来存储一组对象,这些对象可以是任意类型。 下面是ArrayList的源码解析: 1. 成员变量 ```java /** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size; ``` ArrayList有三个成员变量,分别是DEFAULT_CAPACITY、EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA。DEFAULT_CAPACITY表示默认的容量大小,EMPTY_ELEMENTDATA是一个空数组,DEFAULTCAPACITY_EMPTY_ELEMENTDATA也是一个空数组,但它会在第一次添加元素时扩容为DEFAULT_CAPACITY大小。elementData是一个Object类型的数组,用于存储ArrayList中的元素,size表示ArrayList中元素的数量。 2. 构造方法 ```java /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // defend against c.toArray (incorrectly) not returning Object[] // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } } /** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } ``` ArrayList提供了三个构造方法。第一个构造方法是无参的构造方法,它将elementData赋值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA。第二个构造方法接收一个Collection类型的参数c,它将参数c中的元素转为数组并将其赋值给elementData。第三个构造方法接收一个int类型的参数initialCapacity,它根据参数initialCapacity的值创建一个Object类型的数组并将其赋值给elementData。 3. 常用方法 常用方法包括add()、get()、set()、remove()、size()等。 add()方法用于在ArrayList中添加一个元素,如果elementData的容量不足,就需要进行扩容。扩容的方式是将elementData数组的大小增加50%。 get()方法用于获取ArrayList中指定位置的元素。 set()方法用于将ArrayList中指定位置的元素替换为指定的元素。 remove()方法用于删除ArrayList中指定位置的元素。 size()方法用于获取ArrayList中元素的数量。 4. 总结 ArrayListJava集合框架中的一个类,它实现了List接口,可以用来存储一组对象。ArrayList的源码解析包括成员变量、构造方法和常用方法。掌握ArrayList的源码可以帮助我们更好地理解它的实现原理,从而更加灵活地应用它。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值