Java-Iterator详解

迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示。

一、概述

前面我们分析了大部分的Java集合框的集合类,比如:ArrayListLinkedListHashMap。他们各自的底层数据机构大都不同, ArrayList 是维护动态数组,LinkedList 是链表结构,HashMap 是依赖于哈希算法的数组和链表、红黑树。Java对这些常用的集合都实现了迭代器(Iterator),从而使我们能在不同的集合类使用同一的遍历方法。
把访问逻辑从不同类型的集合类中抽取出来,不用直接和集合进行打交道,而是控制 Iterator 向它发送向前向后的指令,就可以遍历集合,从而避免向外部暴露集合的内部结构。

二、Iterator

1. 遍历

迭代器是可以从前往后,或者从后往前遍历的。
为遍历不同聚集结构提供如:开始,下一个,是否有下一个,是否结束,当前哪一个等等的一个统一接口。

public interface Iterator<E> {
   // 是否有下一个
    boolean hasNext();

   // 获取下一个
    E next();

  // 移除迭代器新返回的元素。
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

// @since 1.8 传入一个方法: 可以对每个元素进行特定操作
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

JDK内置的集合类,大多都可以直接使用 Iterator 遍历:

public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("list1");
        arrayList.add("list2");
        arrayList.add("list3");
        printIterator(arrayList.iterator());

        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("linked1");
        linkedList.add("linked2");
        linkedList.add("linked3");
        printIterator(linkedList.iterator());

        HashSet<String> set = new HashSet<>();
        set.add("set1");
        set.add("set2");
        set.add("set3");
        printIterator(set.iterator());
    }

    public static void printIterator(Iterator iterator){
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

2. fail-fast

使用 Iterator 遍历集合时,不能使用集合类的方法来添加或者删除来改变集合的大小。

public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("list1");
        arrayList.add("list2");
        arrayList.add("list3");
        Iterator<String> iterator = arrayList.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
//            arrayList.add("cc");
            arrayList.remove("list3");
        }
}

运行时会抛 ConcurrentModificationException 异常。
唯一完全的的方法是使用 Iterator 的方法 remove 方法。

public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("list1");
        arrayList.add("list2");
        arrayList.add("list3");
        Iterator<String> iterator = arrayList.iterator();
        while (iterator.hasNext()){
            String item = iterator.next();
            if (item.equals("list1")){
                iterator.remove();
            }
        }
      arrayList.forEach(System.out::println);
}
// 打印结果:
list2
list3

我们来看下 ArrayList 里面实现的 Iterator:

public Iterator<E> iterator() {
        return new Itr();
}

private class Itr implements Iterator<E> {
    // 游标 可以理解成 两个元素之间的指针
    int cursor;       // index of next element to return
   // 当前迭代器 迭代的最新下标
    int lastRet = -1; // index of last element returned; -1 if no such
   // 获取当前 ArrayList的 版本号
    int expectedModCount = modCount;

// 是否还有下一个元素  
    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
// 移除操作
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
// 重新复制一次 当前ArrayList的版本号
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;
        checkForComodification();
    }

// 检查 当前ArrayList的版本号 是否已发送变化
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

抛异常的核心方法是 checkForComodification(),比较创建 Iterator 的时候的 ArrayList 的版本号和当前 ArrayList 的版本号是否一样,不一样则抛异常。ArrayListaddremove 都会使 modCount 加1,所以在使用 Iterator 遍历的时候,调用集合本身的添加或删除就会改变modCount从而造成前后不一致,就会抛异常。

当然,也不是绝对的,看源码我们可以发现,只有调用了checkForComodification方法才会触发检查,而检查是否存在下一个hasNext方法是不会触发检查的。所以我们可以在遍历到倒数第二个的时候调用集合的移除方法,从而在下一次执行 hasNext 的时候返回false。 然后退出循环,达到在 Iterator 里循环也能移除元素的目的:

public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("list1");
        arrayList.add("list2");
        arrayList.add("list3");
        Iterator<String> iterator = arrayList.iterator();
        while (iterator.hasNext()){
            String item = iterator.next();
            if (item.equals("list2")){
                arrayList.remove("list1");
            }
        }
       arrayList.forEach(System.out::println);
}

打印结果为:

list2
list3

结果正常输出,不会报错,正常移除我们要移除的元素,当然也可以移除任意存在的元素(没屁用的知识又增加了)。 这种方法还是不提倡,推荐使用官方已经提供的 Iterator 的移除方法。

3. forEach

JDK 1.5的时候引入了增强for循环(for each)

public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("list1");
        arrayList.add("list2");
        arrayList.add("list3");
        for (String item : arrayList) {
            System.out.println(item);
        }
    }

for each只是语法糖,节省代码,节省操作,不用关心下标的起始值和终止值。本质还是 Iterator
我们来看编译后的文件:

public static void main(String[] var0) {
        ArrayList var1 = new ArrayList();
        var1.add("list1");
        var1.add("list2");
        var1.add("list3");

    // 本质还是使用 Iterator的 遍历
        Iterator var2 = var1.iterator();
        while(var2.hasNext()) {
            String var3 = (String)var2.next();
            System.out.println(var3);
        }
    }

可以看到,本质还是使用了 Iterator 的遍历,所以我们在使用 for each的时候不要操作集合的删除或添加方法。会抛 ConcurrentModificationException 异常,原因刚刚分析了。

三、Iterable

Iterable 接口实现后的功能是‘返回’一个迭代器。是 Java 集合框架的顶级接口。

public interface Iterable<T> {

    // 返回一个 迭代器
    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.8 可分割迭代器: 并行处理
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

四、ListIterator

ListIterator 继承于 Iterator,增加了很多方法。

public interface ListIterator<E> extends Iterator<E> {

    // 是否存在下一个
    boolean hasNext();

   // 获取下一个
    E next();

   // 游标前是否存在元素
    boolean hasPrevious();
 
   // 获取游标的前一个元素
    E previous();

   // 获取游标后面的 元素索引
    int nextIndex();

   // 获取游标前面的 元素索引
    int previousIndex();

   // 移除迭代器新返回的元素
    void remove();
   
   // 更新迭代器最后一次操作的元素为 E,也就是更新最后一次调用 next() 或者 previous() 返回的元素。
    void set(E e);
 
    // 插入一个元素
    void add(E e);
}

相比于 Iterable 增加多个方法: 添加,修改,获取前一个元素,获取下标...
还是已 ArrayList 为例,看下它实现的 ListIterator :

// 返回一个 ListIterator对象
public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

private class ListItr extends Itr implements ListIterator<E> {
    // 游标位置从什么地方开始
    ListItr(int index) {
        super();
        cursor = index;
    }

    public boolean hasPrevious() {
        return cursor != 0;
    }

    public int nextIndex() {
        return cursor;
    }

    public int previousIndex() {
        return cursor - 1;
    }

   // 获取游标的前一个元素 
   // 游标 和 迭代器的最新下标 都往前移动一格
    @SuppressWarnings("unchecked")
    public E previous() {
        checkForComodification();
        int i = cursor - 1;
        if (i < 0)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i;
        return (E) elementData[lastRet = i];
    }

   // 修改方法
    public void set(E e) {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.set(lastRet, e);
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

   //  添加
    public void add(E e) {
        checkForComodification();

        try {
            int i = cursor;
            ArrayList.this.add(i, e);
            cursor = i + 1; // 插入之后 游标 往后移动
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}

可以很简单的知道原理,都是通过游标来操作对应数组的下标。
所以也能推导出为什么 ListIterator 不适用于 Set类型,而且 Set 类型也没有类型的增强的可添加的 Iterator 原因,因为本质还是操作数组下标,而 Set 的的插入是不能指定下标的。

ListIterator 和 Iterator 的区别

对比各自的源码API 我们可以发现:

  1. ListIterator 继承与 Iterator ,所以ListIteratorIterator 的所有功能。
  2. ListIterator 还有往前获取元素的功能,可以双向遍历。
  3. ListIterator 可以获取当前迭代器的前后元素的下标。
  4. ListIterator 具备修改功能,可修改最新迭代的元素。

后记

Java集合可能暂告一段落。接下来可能分享java基础,JVM,多线程的理解。写文章不能是东抄抄西抄抄,还得有干货有自己的理解输出,所以会先自己理解源码然后参考权威书籍,尽最大努力输出干货,心得。
已经获得一个offer,总体来说比现在的好,虽然可能还不够好,不过我相信自己继续努力,终会更好。

量变引发质变,经常进步一点点,期待蜕变的自己。

©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容