Java 集合框架系列二:JDK 1.8 Iterable 和 Iterator、Enumerator 和 ListIterator 详解

当我们想要遍历集合时,Java 为我们提供了多种选择,通常有以下三种写法:

  • 写法 1:for 循环
    for (int i = 0, len = strings.size(); i < len; i++) {
     
        System.out.println(strings.get(i));
    }
  • 写法 2:foreach 循环
    for (String var : strings) {
     
        System.out.println(var);
    }
  • 写法 3:Iterator
    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

Iterable

那么,为什么集合可以进行 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

Iterator 是迭代器的意思,是 foreach 遍历的主体。foreach 主要是解决 for 循环依赖下标的问题,为高效遍历更多的数据结构提供了支持。通过 Iterator 的类文档可以了解到如下信息:

  • Iterator 是集合的迭代器,一般每个集合实现类需要实现 Iterator 提供自己的迭代器,为提供高效遍历提供支持,因为不同的集合可能使用了不同的数据结构,如 ArrarList 使用了 数组,LinkedList 使用了链表。
  • Iterator 是用来替代 JDK 1.0 提供的 Enumeration 类。在 Iterator 出现之前,Hashtable、Vector 等都是通过 Enumeration 进行迭代的,相比于 Enumeration,Iterator 做了如下改进:1.允许迭代过程中删除元素。2.方法名进行了改进。
  • 另外 Enumeration 是不支持 fail-fast 策略的,且 Enumeration 不支持同步,而是 Hashtable、Vector 等类实现 Enumeration 时添加了同步。
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());
    }
}

Iterable 和 Iterator 有什么关系

  • 实现了 Iterator 接口的类就具有迭代元素的能力,实现了 Iterable 的类就具备 for-each 的能力。
  • Iterable 可以返回 Iterator 实例,所以实现了 Iterable 的类就可以使用 Iterator。
  • 集合 Collection、List、Set 都是 Iterable 的实现类,所以他们及其他们的子类都可以使用 for-each 进行迭代,而且如 ArrayList 等都在内部创建了私有的 Iterator 的实现类作为迭代器。

为什么不把 Iterable 和 Iterator 合并到 Iterable 中?

  • 因为一个类可以实现多种 Iterator,提供多种迭代器,如果把 Iterator 的功能合并到 Iterable 中,无疑会增加实现的难度。
  • 单一职责原则。

ListIterator

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);
}

ListIterator 使用

/**
 * 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

Enumerator 实现了 Enumeration 接口,Enumeration 接口不支持对元素的删除操作。

public interface Enumeration<E> {
     
    boolean hasMoreElements();
    E nextElement();
}

你可能感兴趣的:(Java,集合框架,java,集合,迭代)