在Java中,我们可以对List集合进行如下几种方式的遍历:第一种就是普通的for循环,第二种为迭代器遍历,第三种是for each循环。后面两种方式涉及到Java中的iterator和iterable对象,接下来我们通过源码来看看这两个对象的区别以及如何在自定义类中实现for each循环。
一、Java.util.Iterator
Java.util.Iterator 接口描述的是以统一的方式对各种集合元素进行遍历 / 迭代的工具,也称“迭代器”。
迭代器( Iterator )模式,又叫做游标( Cursor )模式,是用于遍历集合类的标准访问方法。 GOF 给出的定义为:提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。
package java.util;
import java.util.function.Consumer;public interface Iterator<E> {//如果迭代具有更多的元素,则返回true
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());
}
}
iterator通过hasNext()
,next()
两个方法定义了对集合迭代访问的方法,而具体的实现方式依赖于不同的实现类,具体的集合类实现Iterator接口中的方法以实现迭代。
二、java.lang.Iterable
Iterable是从jdk1.5就存在的接口,其实我们经常用到它的功能,就是for-each,要想使用for-each,就必须实现此接口
package java.lang;import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;//实现此接口允许对象成为“for-each loop”语句的目标
public interface Iterable<T> {
//返回类型为 T元素的迭代器。 since 1.5
Iterator<T> iterator();
//对迭代器中的所有的元素进行某项处理,直到所有的都被处理或者出现异常 since 1.8
default void forEach(Consumer<? super T> action) {Objects.requireNonNull(action);for (T t : this) {action.accept(t);}
}
/*返回一个可分割的迭代器 since 1.8Spliterator,可分割的迭代器,是1.8退出的用于并行遍历元素而设计的一个迭代器官方文档说明默认的实现分割的能力比较差,推荐覆盖默认实现。可以跟上面的Iterator功能区分;一个是顺序遍历,一个是并行遍历
*/
default Spliterator<T> spliterator() {return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
在List中并没有实现Iterator接口,而是实现的Iterable接口。观察Iterable接口的源码可以发现其只是返回了一个Iterator对象。
注意
并不是实现了Iterable接口的类才能使用foreach遍历,数组就没有实现Iterable接口,数组使用foreach,反编译后的代码其实是通过for循环来完成这个遍历的功能。
- 1.8新增了两个默认实现:一个是
foreach
,一个是Spliterator
foreach
和Spliterator
一个是顺序遍历元素,一个是并行遍历元素
三、迭代器原理
java 集合类库的迭代器跟其他类库的迭代器在概念上有着重要的区别。比如:C++的标准模板库的迭代器是根据数组索引建模的。如果给定这样一个迭代器,就可以查看指定位置上的元素,就像是知道数组索引i,就可以查看数组元素a[i]一样,不需要查找元素,就可以将迭代器向前移动一个位置
。但是Java迭代器并不是如此。
java迭代器查找操作和位置变更是紧密相连的,查找元素的唯一方式就是调用next,而在执行查找的同时,迭代器位置随之向前移动
,因此,应该将java迭代器 认为是位于两个元素之间。当调用next时候,迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用。