JAVA中循环遍历list有三种方式:for循环、增强for循环(即foreach循环)、iterator遍历。
创建ArrayList:
List
list.add("AA");
list.add("BBB");
list.add("CCCC");
list.add("DDDD");
list.add("EEE");
示例:删除长度为4的字符串元素。
for (int i = 0; i < list.size(); i++) {
if (list.get(i).length() == 4) {
list.remove(i);
}
}
验证输出结果:
for (String s : list) System.out.print(s + ",");
输出结果为:AA,BBB,DDDD,EEE,
错误之处:DDDD元素竟然没有删除掉。
问题分析:
这种方式的问题在于,删除某个元素后,list的大小size发生了变化,而你的索引也在变化,所以会导致你在遍历的时候漏掉某些元素。比如当你删除第1个元素后,继续根据索引访问第2个元素时,因为删除的关系后面的元素都往前移动了一位,所以实际访问的是第3个元素。<见源码分析>;不会报出异常,只会出现漏删的情况;如果只是删除一个元素,就break,可以使用这种方式。
适用场景:
因此,这种方式可以用在删除特定的一个元素时使用,但不适合循环删除多个元素时使用。
2. foreach循环删除元素
删除一个元素之后,无论是否还有满足条件的元素,都必须跳出循环break,否则报出java.util.ConcurrentModificationException。
因此,也只能用于删除一个元素。
//删除元素后必须break跳出,否则报出异常
for (String s : list) {
if (s.length() == 4) {
list.remove(s);
break;
}
}
for (String s : list) System.out.print(s + ",");//AA,BBB,DDDD,EEE,
这种方式的问题在于,删除元素后继续循环会报错误信息ConcurrentModificationException,因为元素在使用的时候发生了并发的修改,导致异常抛出。但是删除完毕马上使用break跳出,则不会触发报错。
使用迭代器删除元素完美。
//迭代器:完美
Iterator iterator = list.iterator();
while(iterator.hasNext()){
if(iterator.next().length()==4){
iterator.remove();
}
}
for (String s : list) System.out.print(s + ",");//AA,BBB,EEE,
这种方式可以正常的循环及删除。但要注意的是,使用iterator的remove方法,如果用list的remove方法同样会报上面提到的ConcurrentModificationException错误。
适合场景:对一个List对象,需要删除大量元素,保留较少元素时,此时可以采用创建一个新的List对象,将需要保留的元素add进新的List对象,然后让旧引用指向新对象即可。
新建一个List对象,将需要保留的元素item添加到新List中,然后原来的引用指向新List即可。
// 4. 创建新对象
ArrayList newList = new ArrayList();
for (int i = 0; i < list.size(); i++) {
if (list.get(i).length() < 3) {//条件为需要保留元素的条件
newList.add(list.get(i));
}
}
list = newList;
for (String s : list) System.out.print(s + ",");//AA,
内部有个size属性,直接返回该size属性。
public int size() {
checkForComodification();
return this.size;
}
原理:将index之后的元素向前移动1个位置;通过native方法-System.arraycopy实现。
并将size减一,并将原list最后一个元素引用置为null,便于垃圾回收GC。
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;
}
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos, int length);
内部有个for循环遍历,遍历一遍找到该元素的索引,然后再调用remove(index)方法删除。
因此,foreach删除元素性能肯定不如普通的for循环。
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;
}
见五、迭代器原理。
迭代器内部也是通过调用remove(index)方法实现的,只不是增加了cursor控制了索引,保证删除元素后,cursor不变。
如果只是删除一个元素,这4种方法都可以实现,但是普通的for循环和迭代器较好,因为遍历过程中索引是已知的;
若循环删除多个元素,只能使用迭代器和创建新对象存储。根据情况使用,一般情况下使用迭代器最好。
迭代器删除:内部也是调用remove(index)方法,只不过是通过cursor控制了索引,在删除元素后cursor不变,不会造成漏删的情况。
与创建新ArrayList相比:创建新ArrayList会消耗更多的内存空间;在删除较多的情况下效率更高些。
public interface Iterator
定义了四种方法:hasNext、next、remove、forEachRemaining
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
迭代器的使用方法:hasNext和next方法结合使用
Iterator
while (iterator.hasNext()) {
if (iterator.next().length() == 4) {
iterator.remove();
}
}
由于List列表这种特殊的集合,可以前后遍历、添加、删除等,继承了Iterator
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void remove();
void set(E e);
void add(E e);
Iterator
一般会设置几个索引属性cursor、lastRet、expectedModCount等结合集合的size属性实现。
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
下面以ArrayList的Iterator实现为例:
ArrayList提供了Iterator的实现类Itr();也提供了ListIterator的ListItr()。
public Iterator iterator() {
return new Itr();
}
public ListIterator listIterator() {
return new ListItr(0);
}
public ListIterator listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
private class Itr implements Iterator {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
cursor自动增1,因此调用next方法之后,光标指向下一个元素。
@SuppressWarnings("unchecked")
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];
}
真正删除的操作还是通过调用ArrayList的remove方法,改变的是索引,对外不暴露索引,删除之后,仍然保持cursor=lastRet不变,这就是与for循环删除的区别。
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();
}
ListItr可以提供一个参数index,表示从哪个位置开始迭代;缺省情况下是从0开始。
/**
* An optimized version of AbstractList.ListItr
*/
private class ListItr extends Itr implements ListIterator {
ListItr(int index) {
super();
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
感悟:之前总觉得迭代器很神秘,查看源码之后,发现so so easy。