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