文章

java重拾-Iterator&Iterable

java重拾-Iterator&Iterable

遍历

可采取的方式有

  • for
for (int i = 0; i < list.size(); i++) {
    System.out.print(list.get(i) + ",");
}
  • iterator
Iterator it = list.iterator();
while (it.hasNext()) {
    System.out.print(it.next() + ",");
}
  • foreach
for (String str : list) {
    System.out.print(str + ",");
}

但其实foreach也只是语法糖, 本质是iterator, 反编译如下

Iterator var3 = list.iterator();

while(var3.hasNext()) {
    String str = (String)var3.next();
    System.out.print(str + ",");
}

看来, iterator是很重要的一个类了

Iterator

public interface Iterator<E> {
  
    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());
    }
}

这是一个接口, 需要实现next 和 remove 即可, 任何实现Iterator的接口的类, 都称之为迭代器.

Iterable

可以理解为一个标签接口, 只要求你实现一个默认的iterator即可

我们可以自己实现很多不同的iterator, 以实现需要的遍历方式

forEach

JDK 1.8 时,Iterable 接口中新增了 forEach 方法。该方法接受一个 Consumer 对象作为参数,用于对集合中的每个元素执行指定的操作。该方法的实现方式是使用 for-each 循环遍历集合中的元素,对于每个元素,调用 Consumer 对象的 accept 方法执行指定的操作。

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

该方法实现时首先会对 action 参数进行非空检查,如果为 null 则抛出 NullPointerException 异常。然后使用 for-each 循环遍历集合中的元素,并对每个元素调用 action.accept(t) 方法执行指定的操作。由于 Iterable 接口是 Java 集合框架中所有集合类型的基本接口,因此该方法可以被所有实现了 Iterable 接口的集合类型使用。

它对 Iterable 的每个元素执行给定操作,具体指定的操作需要自己写Consumer接口通过accept方法回调出来。

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
list.forEach(integer -> System.out.println(integer));

foreach陷阱

在使用foreach时, 不要对元素进行直接的删除, 或者说, 不要对该集合做修改(可以改元素不能改集合)

List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");

for (String str : list) {
	if ("沉默王二".equals(str)) {
		list.remove(str);
	}
}

System.out.println(list);

报错如下

img

可以发现, 在Iterator的next方法中, 检查了是否存在并发的修改

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

正确地删除元素

remove 后 break

List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");

for (String str : list) {
	if ("沉默王二".equals(str)) {
		list.remove(str);
		break;
	}
}

break 后循环就不再遍历了,意味着 Iterator 的 next 方法不再执行了,也就意味着 checkForComodification 方法不再执行了,所以异常也就不会抛出了。

但是呢,当 List 中有重复元素要删除的时候,break 就不合适了。

for 循环

List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
for (int i = 0; i < list.size(); i++) {
	String str = list.get(i);
	if ("沉默王二".equals(str)) {
		list.remove(str);
	}
}

for 循环虽然可以避开 fail-fast 保护机制,也就说 remove 元素后不再抛出异常;但是呢,这段程序在原则上是有问题的。为什么呢?

第一次循环的时候,i 为 0,list.size() 为 3,当执行完 remove 方法后,i 为 1,list.size() 却变成了 2,因为 list 的大小在 remove 后发生了变化,也就意味着“沉默王三”这个元素被跳过了。能明白吗?

remove 之前 list.get(1) 为“沉默王三”;但 remove 之后 list.get(1) 变成了“一个文章真特么有趣的程序员”,而 list.get(0) 变成了“沉默王三”。

使用 Iterator

List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");

Iterator<String> itr = list.iterator();

while (itr.hasNext()) {
	String str = itr.next();
	if ("沉默王二".equals(str)) {
		itr.remove();
	}
}

为什么使用 Iterator 的 remove 方法就可以避开 fail-fast 保护机制呢?看一下 remove 的源码就明白了。

public void remove() {
    if (lastRet < 0) // 如果没有上一个返回元素的索引,则抛出异常
        throw new IllegalStateException();
    checkForComodification(); // 检查 ArrayList 是否被修改过

    try {
        ArrayList.this.remove(lastRet); // 删除上一个返回元素
        cursor = lastRet; // 更新下一个元素的索引
        lastRet = -1; // 清空上一个返回元素的索引
        expectedModCount = modCount; // 更新 ArrayList 的修改次数
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException(); // 抛出异常
    }
}

删除完会执行 expectedModCount = modCount,保证了 expectedModCount 与 modCount 的同步。

License:  CC BY 4.0