概述
查看Collection接口的定义,发现其继承了Iterable接口,而后者又聚合了一个成员类,即Iterator。因此就先瞅瞅这个接口的定义细节。
Iterator,即我们常用的迭代器,取代了老式的Enumeration。能够允许我们在不了解集合序列的底层结构时,轻松遍历并操作序列中的对象。因为创建代价小,因此迭代器也被视为“轻量级”对象。JDK8源码中Iterator接口只包含了4个方法:
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()方法
通过反复调用next方法,可以逐个访问集合中的每个元素。但是,如果到达了集合的末尾,next方法将抛出一个NoSuchElementException
异常。因此next方法需要配合hasNext方法使用。
Iterator迭代器的查找操作和位置变更时紧密联系的。查找一个元素的唯一方法就是调用next,而在执行查找操作的同时,迭代器的位置随之向前移动。因此,应该将java迭代器认为是位于两个元素之间。当调用next时,迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用。 -- 《java核心技术》
hasNext()方法
hasNext主要是用来判断当前迭代器是否还有元素可供访问,以避免上述next方法抛出异常。
boolean hasNext();
复制代码
remove()方法
用来删除上次调用next方法返回的元素。值得注意的是,next方法和remove方法的调用具有互相依赖的关系。如果调用remove之前没有调用next方法将会抛出IlleagalStateException
异常。通俗来讲,如果想remove一个元素,则必须先用hasNext判断迭代器是否还有元素可删,如果有则必须再调用next方法“越过”这个元素,最后删除刚刚“越过”的元素。因此,开发时,存在想删除两个元素,就一口气连续调用两次remove方法,明显就是错误的做法。如下:
--------------Error----------
it.remove();
it.remove(); 连续调用两次remove,Eorror
-------------Correct---------
it.remove();
it.next();
it.remove();
复制代码
举例:遍历List
public static void main(String[] args) {
List<String> xxBrothers = new ArrayList<>();
xxBrothers.add("王宝强");
xxBrothers.add("陈羽凡");
xxBrothers.add("贾乃亮");
Iterator<String> it = xxBrothers.iterator();
while (it.hasNext()){
System.out.println(it.next());
it.remove();
}
System.out.println(xxBrothers.size());
}
---------output---------
王宝强
陈羽凡
贾乃亮
0
复制代码
源码中方法的定义:
default void remove() { // 使用了JDK8的默认方法的新特性
throw new UnsupportedOperationException("remove");
}
复制代码
forEachRemaining(Consumer)方法
forEachRemaining是在JDK1.8添加的方法,其特点是:Lambda表达式结合迭代器遍历集合吗。其实现如下:
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
复制代码
遍历List集合:
public static void main(String[] args) {
List<String> xxBrothers = new ArrayList<>();
xxBrothers.add("王宝强");
xxBrothers.add("陈羽凡");
xxBrothers.add("贾乃亮");
Iterator<String> it = xxBrothers.iterator();
it.forEachRemaining(who -> {
System.out.println(who);
});
---------output---------
王宝强
陈羽凡
贾乃亮
复制代码
特别的是,不能在lamba表达式修改集合元素
Q&A
为什么要使用迭代器,而不是使用索引方式遍历数组
Iterator设计的初衷是为了把遍历一个集合的API进行统一。无论是Array、LinkList、ArrayList或是其他集合,都可以使用迭代器进行统一遍历,而不用care集合的数据结构和实现细节。一句话:Iterator把一个集合的实现和遍历进行了分离。
Iterator接口的作用
接口是一种规范,因此Iterator接口就是规定了各个集合实现该接口时应该遵循的规范。同时,接口也只是一种规范,至于怎么实现这种规范,每个集合会根据自己内部的数据结构等情况考虑实现方式。如对于remove()方法的实现,ArrayList是调用其本身的remove() 方法删除最后一个位置上的元素,然后元素数量减一即可。而对于LinkList的remove()方法,则更多的是通过"指针"的移动来实现删除操作。