当我们想要遍历集合时,Java 为我们提供了多种选择,通常有以下三种写法:
for (int i = 0, len = strings.size(); i < len; i++) {
System.out.println(strings.get(i));
}
for (String var : strings) {
System.out.println(var);
}
Iterator iterator = strings.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
那么以上三种遍历方式有何区别呢?for 循环就是根据下标来获取元素,这个特性与数组十分吻合。foreach 则主要对类似链表的结构提供遍历支持,链表没有下标。Iterator 就是我们今天要讲述的主角,事实上 foreach 循环就是 Java 的语法糖,虚拟机在使用前端编译器将 .java 文件编译为 .class 字节码文件时会把 foreach 转换为 Iterator 迭代器遍历的方式。如下例子:
源码
public void testForEach(){
List<String> list = new ArrayList<>();
for (String s : list) {
}
}
字节码
0 new #2 <java/util/ArrayList>
3 dup
4 invokespecial #3 <java/util/ArrayList.<init>>
7 astore_1
8 aload_1
9 invokeinterface #4 <java/util/List.iterator> count 1
14 astore_2
15 aload_2
16 invokeinterface #5 <java/util/Iterator.hasNext> count 1
21 ifeq 37 (+16)
24 aload_2
25 invokeinterface #6 <java/util/Iterator.next> count 1
30 checkcast #7 <java/lang/String>
33 astore_3
34 goto 15 (-19)
37 return
那么,为什么集合可以进行 foreach 遍历,而我们自己定义的 Java 对象却不可以呢?
Iterable 是可迭代的意思,作用是为集合类提供 foreach 循环的支持,所以 Iterable 也是在 JDK 1.5 才开始提供的。由于使用 for 循环需要通过位置获取元素,而这种获取方式仅有数组支持。Iterable 就可以让不同的集合类自己提供遍历的最佳方式。在 Iterable 的类文档里只有一句话:实现此接口允许对象成为 “for-each 循环”语句的目标。
Iterable 的 主要方法是返回一个Iterator
对象,如果想让一个 Java 对象支持 foreach,只要实现 Iterable 接口,就可以像集合那样,通过 Iterator iterator = strings.iterator()
方式或者使用 foreach 进行遍历了。
public interface Iterable<T> {
/**
* 返回类型为 T 的元素上的迭代器。
*/
Iterator<T> iterator();
/**
* 对迭代器的每个元素执行给定的操作,直到处理完所有元素或该操作引发异常为止。除非实现类另行指定,
* 否则此操作将以迭代顺序执行(如果指定了迭代顺序),迭代过程中的异常将抛给调用者。
*
* @throws 如果指定的 action 是 null 则抛出 NullPointerException
* @since 1.8
*/
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
/**
* 在此 Iterable 描述的元素上创建 Spliterator。
*
* 默认实现从 Iterable 的 Iterator 创建一个 early-binding Spliterator
* Spliter 继承了 Iterable 的迭代器的 fail-fast 属性。
* 通常应重写默认实现。默认实现返回的 Spliterator 具有较差的拆分能力。实现类几乎总能提供更好的实现。
*
* @since 1.8
*/
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
Spliterator
是JDK 1.8 引入的一种并行遍历的机制,我们熟悉的Iterator
也提供了对集合数据进行遍历的能力,但后者是顺序遍历,前者是并行遍历,这里不详细介绍。
Iterator 是迭代器的意思,是 foreach 遍历的主体。foreach 主要是解决 for 循环依赖下标的问题,为高效遍历更多的数据结构提供了支持。通过 Iterator 的类文档可以了解到如下信息:
public interface Iterator<E> {
/**
* 如果迭代有更多元素,则返回 true
*/
boolean hasNext();
/**
* 返回迭代中的下一个元素
* @throws 如果没有下个元素则抛出 NoSuchElementException
*/
E next();
/**
* 从基础集合中删除最后一个元素,此方法在调用 next() 方法后最多执行一次
*
* @throws 如果不支持此操作则抛出 UnsupportedOperationException
* @throws 如果没有调用 next() 方法或者调用 next() 方法后已经调用了 remove() 方法则抛出 IllegalStateException
*/
default void remove() {
throw new UnsupportedOperationException("remove");
}
/**
* 对剩余的每个元素执行给定的操作,直到所有元素都已处理或操作引发异常。
* 如果指定了迭代顺序,则按迭代顺序执行操作。操作引发的异常被中继到调用方。
*
* @throws 如果指定的 action 是 null 则抛出 NullPointerException
* @since 1.8
*/
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
ListIterator 也是在 JDK 1.2 提供的迭代器,它继承了 Iterator 接口,它只能用于 List 的访问。ListIterator 允许沿双向遍历列表,在迭代期间修改列表,并获得迭代器在列表中的当前位置。ListIterator 没有当前元素;它的游标位置始终位于调用 previous() 返回的元素和调用 next() 返回的元素之间。长度为 n 的列表的迭代器有 n+1 个可能的游标位置。
public interface ListIterator<E> extends Iterator<E> {
// Query Operations
/**
* 如果存在下一个元素返回 true
*/
boolean hasNext();
/**
* 返回下一个元素,如果没有则抛出 NoSuchElementException
*/
E next();
/**
* 如果存在前一个元素返回 true
*/
boolean hasPrevious();
/**
* 返回前一个元素,如果没有则抛出 NoSuchElementException
*/
E previous();
/**
* 返回后续调用 next 将返回的元素的索引。(如果迭代器位于列表末尾,则返回列表大小,在开头则是 0。)
*/
int nextIndex();
/**
* 返回后续调用 previous 将返回的元素的索引。(如果迭代器位于列表的开头,则返回-1。)
*/
int previousIndex();
// Modification Operations
/**
* 从列表中删除 next 或 previous 返回的最后一个元素。
* 只有在调用 next 或 previous 之后还没有调用 add 才能执行此操作且只能调用一次否则抛出 IllegalStateException 。
* 如果不支持此操作抛出 UnsupportedOperationException
*/
void remove();
/**
* 用指定的元素替换 next 或 previous 返回的最后一个元素
* 只有在上一次调用 next 或 previous 之后没有调用 remove 或 add 能进行此调用
*
* @throws 迭代器不支持此操作抛出 UnsupportedOperationException
* @throws 指定的元素类型不匹配抛出 ClassCastException
* @throws 指定的元素无法添加到列表中抛出 IllegalArgumentException
* @throws 调此方法之前没有调 next 或 previous 方法抛出 IllegalStateException
* 调 next 或 previous 方法后调了 remove 或 add 方法又调用当前方法抛出 IllegalStateException
*/
void set(E e);
/**
* 在列表中插入指定的元素,元素会插入到 next 将返回的元素之前(如果有)以及 previous 将返回的元素之后(如果有)
*
* @throws 迭代器不支持此操作抛出 UnsupportedOperationException
* @throws 指定的元素类型不匹配抛出 ClassCastException
* @throws 指定的元素无法添加到列表中抛出 IllegalArgumentException
*/
void add(E e);
}
/**
* JDK 1.8 {@link ListIterator} 测试
*
* @author Cruise
* @version 1.0
* @since 2020/9/8
*/
public class ListIteratorTest {
private ArrayList<String> list = new ArrayList<>();
@Before
public void init(){
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
}
/**
* 测试向后遍历
*
* 0,-1
* a
* 1,0
* b
* 2,1
* c
* 3,2
* d
* 4,3
* e
* 5,4
* f
* 6,5
*/
@Test
public void testNext(){
ListIterator<String> iterator = list.listIterator();
System.out.println(iterator.nextIndex() + "," + iterator.previousIndex());
while (iterator.hasNext()) {
System.out.println(iterator.next());
System.out.println(iterator.nextIndex() + "," + iterator.previousIndex());
}
}
/**
* 测试向前遍历
*
* 6,5
* f
* 5,4
* e
* 4,3
* d
* 3,2
* c
* 2,1
* b
* 1,0
* a
* 0,-1
*
*/
@Test
public void testPrevious(){
// 定位到列表末尾
ListIterator<String> iterator = list.listIterator(list.size());
System.out.println(iterator.nextIndex() + "," + iterator.previousIndex());
while (iterator.hasPrevious()) {
System.out.println(iterator.previous());
System.out.println(iterator.nextIndex() + "," + iterator.previousIndex());
}
}
@Test
public void testSet(){
ListIterator<String> iterator = list.listIterator(list.size());
while (iterator.hasPrevious()) {
// iterator.set("1"); 抛出 IllegalStateException
String previous = iterator.previous();
if ("e".equals(previous)) {
//iterator.remove();// 先 remove 抛出 IllegalStateException
// iterator.set("1");
// iterator.add("1");// 先 add 抛出 IllegalStateException
iterator.set("2");
}
}
}
}
Enumerator 实现了 Enumeration 接口,Enumeration 接口不支持对元素的删除操作。
public interface Enumeration<E> {
boolean hasMoreElements();
E nextElement();
}