Iterator
是集合框架的重要组成部分,用来封装集合的遍历。ListIterator
扩展了 Iterator
,添加了一些针对 List
的方法,比如向前遍历、添加、更新。同时迭代器在使用时会有一些坑。
Iterator
简介、测试
public interface Iterator<E> {
/**
* 迭代器没有到达末尾则返回 true
*/
boolean hasNext();
/**
* 返回下一个元素,或者抛出异常 NoSuchElementException(已经到达末尾时)
*/
E next();
/**
* 删除迭代器最后一次返回的元素,或者抛出异常 IllegalStateException
*/
default void remove() {
throw new UnsupportedOperationException("remove");
}
/**
* 对每个剩余元素执行给定的操作
*/
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
remove()
方法需要特别注意,它的功能是删除迭代器最后一次返回的元素,且每次调用 next()
方法后只能调用一次 remove()
方法,最后一次返回的元素指的是:
next()
方法返回的元素forEachRemaining()
迭代的最后一个元素,大概率是集合的最后一个元素(但发生了并发修改就可能不是集合的最后一个元素了,详见下面的源码分析)
remove 注意点:
- 调用
remove()
方法前必须先调用next()
或forEachRemaining()
方法 - 每次
next()
调用后最多只能调用一次remove()
方法
以下代码会报错,因为调用 remove()
方法前没有调用 next()
方法,导致不知道要删除哪个元素:
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4);
Iterator<Integer> iterator = list.iterator();
iterator.remove();
}
Exception in thread "main" java.lang.IllegalStateException
at java.util.ArrayList$Itr.remove(ArrayList.java:874)
at com.example.heima.itr.ItrTest.main(ItrTest.java:21)
以下代码同样会报错(在第二次调用 remove
() 方法时),即使先调用了两次 next()
方法,因为 remove()
方法只会删除上一次返回的元素,不会关注上上次返回的:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
Iterator<Integer> iterator = list.iterator();
iterator.next();
iterator.next();
iterator.remove();
System.out.println(list);
iterator.remove();
}
[1, 3, 4]
Exception in thread "main" java.lang.IllegalStateException
at java.util.ArrayList$Itr.remove(ArrayList.java:874)
at com.example.heima.itr.ItrTest.main(ItrTest.java:23)
以下代码会正常执行:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
Iterator<Integer> iterator = list.iterator();
iterator.next();
iterator.remove();
System.out.println(list);
iterator.next();
iterator.remove();
System.out.println(list);
}
[2, 3, 4]
[3, 4]
以下代码会删除最后一个元素 “4”:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
Iterator<Integer> iterator = list.iterator();
iterator.forEachRemaining(System.out::println);
iterator.remove();
System.out.println(list);
}
1
2
3
4
[1, 2, 3]
ArrayList 的 Iterator 实现
可以通过 ArrayList
的源码来更好的理解 Iterator
,使用 lastRet
记录最后一次返回的元素的索引,默认值为 -1 代表没有最后一次返回的元素。next()
、forEachRemaining()
方法会修改 lastRet
。remove()
方法会使用并重置 lastRet
:
private class Itr implements Iterator<E> {
int cursor; // 下一个元素的索引
int lastRet = -1; // 最后一次返回的元素的索引,若没有则返回 -1
int expectedModCount = modCount; // 实例化迭代器时会记录当前 ArrayList 的 modCount,用于并发修改的检查
Itr() {}
// 下一个元素的索引为 size 说明到末尾了
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
// 并发修改检查
checkForComodification();
// 记录当前的 cursor
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
// cursor++
cursor = i + 1;
// 使用 lastRet 记录加一之前的 cursor,即上次返回的元素的索引,由于 remove
return (E) elementData[lastRet = i];
}
public void remove() {
// 之前没有调用过 next() 方法,
// 或之前调用过 next() 方法,但之后已经调用过 remove() 了,因为 remove() 会将 lastRet 重置为 -1
if (lastRet < 0)
throw new IllegalStateException();
// 并发修改检查
checkForComodification();
try {
// 删除上次返回的元素 lastRet
ArrayList.this.remove(lastRet);
// cursor--
cursor = lastRet;
// 重置 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();
}
// 判断条件有 modCount == expectedModCount,所以遇到并发修改会停止迭代
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();
}
}
ListIterator
简介、测试
是专门用于 List
的迭代器,允许从任意方向(正向、反向)遍历列表,在迭代期间修改列表,并获取迭代器在列表中的当前位置。ListIterator
没有当前元素;它的游标总是位于调用 previous()
返回的元素和调用 next()
返回的元素之间。对于长度为 n 的列表,迭代器有 n+1 个可能的游标位置,如下所示:
Element(0) Element(1) Element(2) ... Element(n-1)
cursor positions: ^ ^ ^ ^ ^
注意,remove()
和 set(Object)
方法不是根据游标位置定义的;而是对调用 next()
、previous()
或 forEachRemaining()
返回的最后一个元素进行操作。
public interface ListIterator<E> extends Iterator<E> {
// Query Operations
/**
* 如果迭代器在正向遍历列表时有更多的元素,则返回 true。
*/
boolean hasNext();
/**
* 返回列表中的下一个元素并推进游标位置。(注意,交替调用 next 和 previous 将重复返回相同的元素。)
*/
E next();
/**
* 如果迭代器在反向遍历列表时有更多的元素,则返回 true
*/
boolean hasPrevious();
/**
* 返回列表中的前一个元素并向后移动游标位置。(注意,交替调用 next 和 previous 将重复返回相同的元素。)
*/
E previous();
/**
* 返回下次调用 next 返回的元素的索引。(如果迭代器位于列表的末尾,则返回列表大小。)
*/
int nextIndex();
/**
* 返回下次调用 previous 返回的元素的索引。(如果迭代器位于列表的开头,则返回 -1。)
*/
int previousIndex();
// Modification Operations
/**
* 从列表中删除最后一个返回的元素。每次调用 next() 或 previous() 后,只能调用一次 remove()。
* 只有在上次调用 next() 或 previous() 后没有调用 add() 时,才能进行此操作。
*/
void remove();
/**
* 最后一个返回的元素替换为指定的元素。只有在对next或previous的最后一次调用之后没有调用remove或add时,才能进行此调用。
* 只有在上次调用 next() 或 previous() 后没有调用 add() 或 remove() 时,才能进行此操作。
*/
void set(E e);
/**
* 将指定的元素插入列表。元素被插入到 next() 返回的元素的前面,以及 previous() 返回的元素的后面。
* (如果列表中不包含元素,则新元素将成为列表中唯一的元素。)
* 在隐式游标之前插入新元素:对 next() 的后续调用将不受影响,而对 previous() 的后续调用将返回新元素。
* (这个调用增加了一个调用 nextIndex()、previousIndex() 返回值。)
*/
void add(E e);
}
交替调用 next 和 previous 将重复返回相同的元素。
public static void main(String[] args) {
List<Integer> list = new LinkedList<>(Arrays.asList(1, 2, 3, 4));
ListIterator<Integer> iterator = list.listIterator(2);
System.out.println(iterator.next());
System.out.println(iterator.previous());
System.out.println(iterator.previous());
System.out.println(iterator.next());
}
3
3
2
2
以下为隐式游标的变化:
1 2 3 4
开始: ^
next: ^ 3
previous: ^ 3
previous: ^ 2
next: ^ 2
remove()
ListIterator
的 remove()
方法与 Iterator
相似,都是删除最后一个返回的元素。最后一次返回的元素可能是:
next()
方法返回的元素previous()
方法返回的元素forEachRemaining()
迭代的最后一个元素
注意点:
- 调用
remove()
方法前必须先调用next()
、previous()
或forEachRemaining()
方法,且在上次调用上述方法后没有调用add()
,才能执行删除。因为add()
会推进游标,且会清除最后一个返回的元素。调用set()
不会影响remove()
,因为set()
不会清除最后一个返回的元素。 - 每次以上方法调用后最多只能调用一次
remove()
方法
以下代码会报错:因为 add()
会推进游标,且会清除最后一个返回的元素,导致 remove()
找不到模目标
public static void main(String[] args) {
List<Integer> list = new LinkedList<>(Arrays.asList(1, 2, 3, 4));
ListIterator<Integer> iterator = list.listIterator();
iterator.next();
iterator.next();
iterator.add(0);
System.out.println(list);
iterator.remove();
}
Exception in thread "main" java.lang.IllegalStateException
at java.util.LinkedList$ListItr.remove(LinkedList.java:923)
at com.example.heima.itr.ItrTest.main(ItrTest.java:20)
set()
set()
功能是替换最后一个返回的元素,前提是最后一个返回的元素必须存在,否则抛异常,和 remove()
的约束类似:
- 调用
set()
方法前必须先调用next()
、previous()
或forEachRemaining()
方法,且在上次调用上述方法后没有调用add()
、remove()
,才能执行此操作。 - 与
remove()
不同的是,set()
方法可多次调用
以下代码正常执行,因为 set()
方法可多次调用:
public static void main(String[] args) {
List<Integer> list = new LinkedList<>(Arrays.asList(1, 2, 3, 4));
ListIterator<Integer> iterator = list.listIterator();
iterator.next();
System.out.println(list);
iterator.set(100);
System.out.println(list);
iterator.set(200);
System.out.println(list);
}
[1, 2, 3, 4]
[100, 2, 3, 4]
[200, 2, 3, 4]
以下代码会报错,因为 remove()
会将游标左移,并清除最后一个返回的元素:
public static void main(String[] args) {
List<Integer> list = new LinkedList<>(Arrays.asList(1, 2, 3, 4));
ListIterator<Integer> iterator = list.listIterator();
iterator.next();
iterator.next();
System.out.println(list);
iterator.remove();
System.out.println(list);
iterator.set(100);
System.out.println(list);
}
[1, 2, 3, 4]
[1, 3, 4]
Exception in thread "main" java.lang.IllegalStateException
at java.util.LinkedList$ListItr.set(LinkedList.java:937)
at com.example.heima.itr.ItrTest.main(ItrTest.java:21)
将上面的 remove
换成 add()
也会报错,因为 add()
会推进游标,且清除最后一个返回的元素:
public static void main(String[] args) {
List<Integer> list = new LinkedList<>(Arrays.asList(1, 2, 3, 4));
ListIterator<Integer> iterator = list.listIterator();
iterator.next();
iterator.next();
System.out.println(list);
iterator.add(0);
System.out.println(list);
iterator.set(100);
System.out.println(list);
}
[1, 2, 3, 4]
[1, 2, 0, 3, 4]
Exception in thread "main" java.lang.IllegalStateException
at java.util.LinkedList$ListItr.set(LinkedList.java:937)
at com.example.heima.itr.ItrTest.main(ItrTest.java:21)
add()
- 元素被插入到
next()
返回的元素的前面,以及previous()
返回的元素的后面。 - 如果列表中不包含元素,则新元素将成为列表中唯一的元素。
- 在隐式游标之前插入新元素:对
next()
的后续调用将不受影响,而对previous()
的后续调用将返回新元素。 - 这个调用增加了一个调用
nextIndex()、previousIndex()
返回值。
public static void main(String[] args) {
List<Integer> list = new LinkedList<>(Arrays.asList(1, 2, 3, 4));
ListIterator<Integer> iterator = list.listIterator();
System.out.println(list);
iterator.next();
System.out.println("nextIndex:" + iterator.nextIndex());
System.out.println("previousIndex:" + iterator.previousIndex());
iterator.add(0);
System.out.println(list);
System.out.println("nextIndex:" + iterator.nextIndex());
System.out.println("previousIndex:" + iterator.previousIndex());
System.out.println("next:" + iterator.next());
System.out.println("previous:" + iterator.previous());
}
[1, 2, 3, 4]
nextIndex:1
previousIndex:0
[1, 0, 2, 3, 4]
nextIndex:2
previousIndex:1
next:2
previous:2
游标的变化:
1 2 3 4
开始: ^
next: ^
1 2 3 4
add: 0^ // 在游标的前面插入,游标前面的元素多了一个,所以要推进游标
1 0 2 3 4
^
next: ^ 2
previous: ^ 2
ArrayList 的 ListIterator 实现
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
// cursor 代表下一个元素的索引,cursor == 0 说明已经到达头部了,一个没有上一个了
public boolean hasPrevious() {
return cursor != 0;
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
@SuppressWarnings("unchecked")
public E previous() {
// 并发修改检查
checkForComodification();
// cursor 代表下一个元素的索引
// 隐式游标始终位于 previous 与 next 之间
// 索引 previous = next - 1 = cursor - 1
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
// 记录上次返回的元素的索引 lastRet
return (E) elementData[lastRet = i];
}
public void set(E e) {
// 判断最后一次返回的元素是否存在
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
// 更新,且没有重置 lastRet,所以能够重复调用 set
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,清除最后一次返回的元素
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
总结
-
游标总是位于调用
previous()
返回的元素和调用next()
返回的元素之间 -
交替调用
next()
和previous()
将重复返回相同的元素 -
remove()
、set()
需要最后一次返回的元素 -
next()
、previous()
、forEachRemaining()
会创建最后一次返回的元素 -
remove()
、add()
会清除最后一次返回的元素 -
set()
可多次调用 -
add()
在隐式游标的前面插入,并推进游标