[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"\u002Farticles\u002Fdesign-pattern-20":26,"\u002Farticles\u002Fdesign-pattern-20\u002Frelated":43,"\u002Farticles\u002Fdesign-pattern-20\u002Fadjacent":72,"\u002Fcategories":90,"render-design-pattern-20":116},{"code":4,"message":5,"data":6},0,"ok",{"author_city":7,"author_years":8,"site_tagline":9,"site_description":10,"skill_one_desc":11,"author_status":12,"skill_two_title":13,"about_hero_image":9,"skill_one_title":14,"icp":15,"author_role":16,"og_default_image":9,"closing_quote_date":17,"toolkit":18,"skill_two_desc":19,"author_bio":9,"site_footer":9,"site_url":20,"author_tagline":21,"weekly_thought":9,"tagline_line_one":22,"author_bio_two":9,"closing_quote":23,"site_name":24,"tagline_line_two":25,"home_hero_image":9},"西安","10","","花开空白 - 个人博客","熟悉Java、Python、Go等后端语言。","努力工作中","前端开发","后端开发","陕ICP备2021009682号-1","开发者","2022年·秋","Java,JavaScript,TypeScript,Python,Go","熟悉Js、Ts语言以及Vue、uniapp等框架。","https:\u002F\u002Firule.cn","很高兴遇见你。","把复杂的事，","把每次一发布，都当成一次和陌生人的握手。","花开空白","写成简单的故事。",{"code":4,"message":5,"data":27},{"id":28,"slug":29,"title":30,"summary":31,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":34,"likeCount":4,"wordCount":35,"readingMinutes":36,"publishedAt":37,"contentMd":38,"contentHtml":39,"tags":40,"metaTitle":9,"metaDescription":9,"keywords":9,"canonicalUrl":9,"robots":41,"ogImage":9,"faq":42,"aiSummary":42},"2113933908541181954","design-pattern-20","设计模式修炼之路 ⑳ 迭代器模式——统一遍历协议","统一遍历协议、for-each 只是语法糖、手写可迭代书架、fail-fast 的哲学","2113897628365230081",1,2334,6515,17,"2024-09-17T21:25:50","> 行为型第七站。\n\n上一篇结尾问了个问题：你每天都在写 `for (String s : list)`，这个冒号背后是谁在工作？为什么 ArrayList、HashSet、LinkedList 长得八竿子打不着，却都能被同一套 for-each 遍历？\n\n答案是 GoF 二十三式里唯一一个被 JDK **语言级收编**的模式——迭代器。这一篇就手写一个能被 for-each 的自定义集合，把遍历协议这件事彻底讲透。\n\n## 一、迭代器模式是什么\n\nGoF 的原始定义：\n\n> Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.\n> （提供一种方法顺序访问一个聚合对象中的各个元素，而又不暴露它的内部表示。）\n\n翻译成人话：**遍历这件事，集合自己最清楚。与其把内部结构摊开给外界看，不如集合派一个\"导游\"出去——导游只回答两个问题：还有下一个吗？带我去下一个。**\n\n四个角色，但有两个 JDK 已经替你写好了：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>角色\u003C\u002Fth>\u003Cth>干什么\u003C\u002Fth>\u003Cth>本文对应\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>Iterator（抽象迭代器）\u003C\u002Ftd>\u003Ctd>统一遍历协议：hasNext \u002F next\u003C\u002Ftd>\u003Ctd>`java.util.Iterator`（JDK 自带）\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>ConcreteIterator（具体迭代器）\u003C\u002Ftd>\u003Ctd>记住游标走到哪，具体怎么走\u003C\u002Ftd>\u003Ctd>BookshelfIterator \u002F 倒序迭代器\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>Aggregate（抽象聚合）\u003C\u002Ftd>\u003Ctd>约定\"能交出迭代器\"\u003C\u002Ftd>\u003Ctd>`java.lang.Iterable`（JDK 自带）\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>ConcreteAggregate（具体聚合）\u003C\u002Ftd>\u003Ctd>你自己的集合类\u003C\u002Ftd>\u003Ctd>Bookshelf\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n注意 `Iterable` 在 `java.lang` 包里——和 String、Object 一个包待遇。for-each 语法糖在编译期只认它：**实现了 Iterable 的类就能被 for-each，这是写进 Java 语言规范的模式**，其他二十二式谁都没有这个地位。\n\n## 二、优点与缺点\n\n**优点：**\n\n1. **遍历协议统一**。数组、链表、哈希表、树、数据库游标……内部结构天差地别，对外全是 hasNext\u002Fnext。调用方一套遍历代码走遍所有容器。\n2. **不暴露内部表示**。集合内部随便重构——数组换链表、链表换数据库——外界零感知。本文书架中途扩容了数组，遍历代码一行没改。\n3. **同一集合，多种遍历**。正序一个迭代器、倒序一个迭代器、过滤版再来一个，还能同时开几个迭代器各走各的，互不干扰。\n4. **职责分离**。聚合管存储，迭代器管遍历，单一职责各安其位。\n\n**缺点：**\n\n1. **简单场景嫌绕**。就一个数组，写个迭代器类纯属仪式感。\n2. **基础版只能单向单游标**。不能回退、不能跳到某位——ListIterator 补了双向和索引课，但那是 List 专属。\n3. **遍历中修改集合是经典雷区**。删着元素遍历着，要么漏元素要么直接炸异常（第六节专门说）。\n4. **每次 next 一层虚调用**。现代 JIT 内联之后基本无感，但极端性能敏感的场景有人在意。\n\n## 三、什么时候用\n\n判断标准：**你写了一个\"容器\"，外界需要逐个访问里面的元素，但你不想（或不该）暴露内部结构。**\n\n- **自定义集合类**：自己写的树、图、跳表、环形缓冲区，想让 for-each 直接生效——本文场景\n- **非内存容器**：数据库游标（ResultSet）、大文件逐行读取、网络流分帧——\"元素\"不在内存里堆着，只能一个一个来\n- **同一集合多种遍历序**：正序、倒序、按权重、按时间\n- **屏蔽实现差异**：底层今天用数组明天换 NoSQL，上层遍历代码不想跟着动\n\n反面对照：用 JDK 集合直接遍历就好，别自己造轮子；一次性下标访问数组，直接来，不必抽象。\n\n## 四、实现方式：让书架被 for-each\n\n### 4.1 反面教材：内部结构裸奔\n\n先看书架不提供迭代器时，外界怎么遍历它：\n\n```java\npublic class BadBookshelf {\n\n    public final Book[] books = new Book[10];   \u002F\u002F public 字段：内部结构直接裸奔\n    public int count;\n\n    public void add(Book book) {\n        books[count++] = book;\n    }\n\n    public Book[] getBooks() { return books; }\n    public int getCount() { return count; }\n    public Book getBook(int index) { return books[index]; }\n}\n```\n\n外界遍历只能这么写：\n\n```java\nfor (int i = 0; i \u003C bad.count; i++) {\n    System.out.println(bad.books[i]);\n}\n```\n\n这行代码成立的前提是外界必须知道三件事：内部是数组、下标从 0 开始、还有一个 count 字段。恶果在测试输出里看得明明白白：\n\n```text\n裸数组长 10，真实书 3 本——容量分配泄露给外界\n外界 raw[0] = null 后，书架第一本书：null\n```\n\n其一，**内部结构的每个细节都是负债**：数组长度 10 这种内存分配细节，居然成了外界可见的事实，哪天书架改成 8 个槽，调用方\"10\"就错了。其二，**外界拿到了就能篡改**：`raw[0] = null` 一行，书架第一本书直接蒸发，书架毫不知情——封装被连锅端。其三，也是最致命的：哪天书架内部从数组换成链表，全世界所有调用方的循环代码集体作废。\n\n### 4.2 正解：实现 Iterable，发导游不发地图\n\n第一步，书架实现 Iterable，内部结构全部私有化，外加自动扩容：\n\n```java\npublic class Bookshelf implements Iterable\u003CBook> {\n\n    private Book[] books = new Book[2];   \u002F\u002F 初始容量故意给小，演示内部扩容\n    private int count;\n\n    public void add(Book book) {\n        if (count == books.length) {\n            Book[] bigger = new Book[books.length * 2];\n            System.arraycopy(books, 0, bigger, 0, books.length);\n            books = bigger;   \u002F\u002F 内部悄悄扩容，外界毫无感知\n        }\n        books[count++] = book;\n    }\n\n    @Override\n    public Iterator\u003CBook> iterator() {\n        return new BookshelfIterator();\n    }\n    \u002F\u002F 倒序迭代器见下文\n}\n```\n\n第二步，迭代器做成书架的**内部类**：\n\n```java\n    \u002F\u002F 迭代器做成内部类：天然访问书架的私有结构，外界只见接口不见实现\n    private class BookshelfIterator implements Iterator\u003CBook> {\n\n        private int cursor = 0;\n\n        @Override\n        public boolean hasNext() { return cursor \u003C count; }\n\n        @Override\n        public Book next() {\n            if (!hasNext()) throw new NoSuchElementException();\n            return books[cursor++];\n        }\n    }\n```\n\n顺手再送一个倒序迭代器，展示\"同一集合、多种遍历策略\"：\n\n```java\n    public Iterator\u003CBook> reverseIterator() {\n        return new Iterator\u003C>() {\n            private int cursor = count - 1;\n\n            @Override\n            public boolean hasNext() { return cursor >= 0; }\n\n            @Override\n            public Book next() {\n                if (!hasNext()) throw new NoSuchElementException();\n                return books[cursor--];\n            }\n        };\n    }\n```\n\n第三步，测试。三段遍历：手动迭代、for-each、倒序：\n\n```java\npublic class IteratorTest {\n\n    public static void main(String[] args) {\n        System.out.println(\"========== 反面教材：内部结构裸奔 ==========\");\n        BadBookshelf bad = new BadBookshelf();\n        bad.add(new Book(\"设计模式\", \"GoF\"));\n        bad.add(new Book(\"重构\", \"Martin Fowler\"));\n        bad.add(new Book(\"Effective Java\", \"Joshua Bloch\"));\n\n        for (int i = 0; i \u003C bad.count; i++) {\n            System.out.println(\"  \" + bad.books[i]);\n        }\n\n        Book[] raw = bad.getBooks();\n        System.out.println(\"  裸数组长 \" + raw.length + \"，真实书 \" + bad.count + \" 本——容量分配泄露给外界\");\n        raw[0] = null;\n        System.out.println(\"  外界 raw[0] = null 后，书架第一本书：\" + bad.getBook(0));\n\n        System.out.println();\n        System.out.println(\"========== 正解：迭代器模式 ==========\");\n        Bookshelf shelf = new Bookshelf();\n        shelf.add(new Book(\"设计模式\", \"GoF\"));\n        shelf.add(new Book(\"重构\", \"Martin Fowler\"));\n        shelf.add(new Book(\"Effective Java\", \"Joshua Bloch\"));\n        shelf.add(new Book(\"代码整洁之道\", \"Robert Martin\"));\n\n        System.out.println(\"-- 手动迭代：hasNext + next --\");\n        Iterator\u003CBook> it = shelf.iterator();\n        while (it.hasNext()) {\n            System.out.println(\"  \" + it.next());\n        }\n\n        System.out.println(\"-- for-each：编译器替你展开成迭代器 --\");\n        for (Book book : shelf) {\n            System.out.println(\"  \" + book);\n        }\n\n        System.out.println(\"-- 倒序迭代：同集合、另一种遍历策略 --\");\n        Iterator\u003CBook> rit = shelf.reverseIterator();\n        while (rit.hasNext()) {\n            System.out.println(\"  \" + rit.next());\n        }\n    }\n}\n```\n\n运行输出：\n\n```text\n========== 反面教材：内部结构裸奔 ==========\n  《设计模式》-GoF\n  《重构》-Martin Fowler\n  《Effective Java》-Joshua Bloch\n  裸数组长 10，真实书 3 本——容量分配泄露给外界\n  外界 raw[0] = null 后，书架第一本书：null\n\n========== 正解：迭代器模式 ==========\n-- 手动迭代：hasNext + next --\n  《设计模式》-GoF\n  《重构》-Martin Fowler\n  《Effective Java》-Joshua Bloch\n  《代码整洁之道》-Robert Martin\n-- for-each：编译器替你展开成迭代器 --\n  《设计模式》-GoF\n  《重构》-Martin Fowler\n  《Effective Java》-Joshua Bloch\n  《代码整洁之道》-Robert Martin\n-- 倒序迭代：同集合、另一种遍历策略 --\n  《代码整洁之道》-Robert Martin\n  《Effective Java》-Joshua Bloch\n  《重构》-Martin Fowler\n  《设计模式》-GoF\n```\n\n### 4.3 三处细节咂摸一下\n\n**细节一：for-each 只是迭代器的语法糖。**`for (Book book : shelf)` 编译器会展开成：\n\n```java\nfor (Iterator\u003CBook> it = shelf.iterator(); it.hasNext(); ) {\n    Book book = it.next();\n    ...\n}\n```\n\njavap 反编译 class 文件可以亲眼验证。所以\"实现 Iterable 就能被 for-each\"不是玄学，是编译器的一行展开——`iterator()` 返回的对象，就是冒号背后工作的那位。\n\n**细节二：迭代器是有状态的，状态就是游标。**BookshelfIterator 的 cursor 字段记着\"我走到第几本了\"。这也是为什么同一个书架能同时开两个迭代器各走各的——每个迭代器持有自己的游标，互不干扰。回头看 ⑱ 命令模式说\"命令有状态\"，迭代器是同款道理：**无状态的对象是工具，有状态的对象是\"一段进行中的事\"**。\n\n**细节三：迭代器做成内部类，是 JDK 的同款选择。**java.util.ArrayList 里的 Itr 就是一个私有内部类——内部类天然访问外部类的私有字段（books、count），外界只见 Iterator 接口，不见任何实现细节。\"聚合藏结构、迭代器做内应\"，封装滴水不漏。\n\n## 五、在框架里找它\n\n**JDK 集合框架——整个大厦的地基。**`Collection extends Iterable`，所以 List、Set、Queue、Deque 全员可迭代；Map 不在体系里，于是它提供了 `entrySet().iterator()` 的入口。你每天写的每个 for-each，都在走这条协议。\n\n**Scanner——一个会读输入的迭代器。**`new Scanner(System.in)` 实现的就是 `Iterator\u003CString>`：hasNext 是\"还有输入吗\"，next 是\"读下一个\"。读文件、读网络流、分词，全是同一套协议换数据源。\n\n**JDBC ResultSet——数据库游标的对象化。**`rs.next()` 把游标往前推一行，返回 false 表示扫完。一张几千万行的表，迭代器协议让你\"像遍历数组一样\"扫完它，而不需要它整个装进内存——迭代器天然适合\"元素不能一次性到齐\"的场景。\n\n**Stream 与 Spliterator——迭代器的 Java 8 进化版。**`list.parallelStream()` 背后是 Spliterator（可拆分迭代器）：把集合切成几段分给多线程并行处理。老 Iterator 只能一个人顺序走，Spliterator 能\"分身\"——这是遍历协议对多核时代的回应。\n\n一句话总结这一节：**凡是要\"一个一个拿出来\"的地方，协议全是 hasNext\u002Fnext，迭代器是数据容器世界的普通话。**\n\n## 六、扩展：fail-fast——遍历中修改集合怎么办\n\n基本版迭代器有一个绕不开的坑：**遍历到一半，集合被改了怎么办？**游标指着第 3 本，有人把第 3 本删了——轻则漏元素、重则死循环。\n\nJDK 集合的答案是 fail-fast（快速失败）：每个集合维护一个版本号 modCount，迭代器创建时记住当时的版本，每次 next 检查版本是否还一致——不一致立刻抛 `ConcurrentModificationException`。\n\n```java\nfor (Book book : shelf) {\n    shelf.add(new Book(\"新书\", \"谁\"));   \u002F\u002F modCount 变了\n    System.out.println(book);            \u002F\u002F 下一次 next() 直接抛异常\n}\n```\n\n设计哲学值得咂摸：**遍历中的修改无法安全处理时，宁可立刻炸给你看，也不悄悄给出错误结果。**快速失败是诚实，静默出错才是灾难。（顺带一提，删除自己刚访问的元素是唯一合法操作：`it.remove()`，迭代器会同步修正游标。）\n\n也有反着来的 fail-safe 策略：`CopyOnWriteArrayList`（⑯ 观察者篇提过它的兄弟思路）遍历时拿的是当时的快照，遍历期间的修改不影响本次遍历——用\"数据可能略旧\"换\"绝不抛异常\"。没有银弹，只有取舍，两个流派各自服侍各自的场景。\n\n## 写在最后\n\n迭代器模式一句话总结：**聚合藏起内部结构，只派发导游；hasNext\u002Fnext 两个问题统一天下遍历，for-each 不过是它的语法糖。**\n\n下一篇 ㉑ 备忘录模式——⑱ 命令模式讲撤销时，我们靠\"逆操作\"轻装回滚。但有些对象没法描述逆操作：游戏角色的血蓝金币要存档、IDE 要恢复整个工作区状态——难道把所有字段全公开出去让外界抄一份？**不破坏封装的前提下，把内部状态存下来、再原样恢复**——命令模式管\"怎么撤销\"，备忘录管\"怎么存档\"。备忘录模式，下一篇见。\n","\u003Cblockquote>\n \u003Cp>行为型第七站。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>上一篇结尾问了个问题：你每天都在写 \u003Ccode>for (String s : list)\u003C\u002Fcode>，这个冒号背后是谁在工作？为什么 ArrayList、HashSet、LinkedList 长得八竿子打不着，却都能被同一套 for-each 遍历？\u003C\u002Fp>\n\u003Cp>答案是 GoF 二十三式里唯一一个被 JDK \u003Cstrong>语言级收编\u003C\u002Fstrong>的模式——迭代器。这一篇就手写一个能被 for-each 的自定义集合，把遍历协议这件事彻底讲透。\u003C\u002Fp>\n\u003Ch2>一、迭代器模式是什么\u003C\u002Fh2>\n\u003Cp>GoF 的原始定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation. （提供一种方法顺序访问一个聚合对象中的各个元素，而又不暴露它的内部表示。）\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>遍历这件事，集合自己最清楚。与其把内部结构摊开给外界看，不如集合派一个\"导游\"出去——导游只回答两个问题：还有下一个吗？带我去下一个。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>四个角色，但有两个 JDK 已经替你写好了：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>角色\u003C\u002Fth>\n   \u003Cth>干什么\u003C\u002Fth>\n   \u003Cth>本文对应\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Iterator（抽象迭代器）\u003C\u002Ftd>\n   \u003Ctd>统一遍历协议：hasNext \u002F next\u003C\u002Ftd>\n   \u003Ctd>`java.util.Iterator`（JDK 自带）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>ConcreteIterator（具体迭代器）\u003C\u002Ftd>\n   \u003Ctd>记住游标走到哪，具体怎么走\u003C\u002Ftd>\n   \u003Ctd>BookshelfIterator \u002F 倒序迭代器\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Aggregate（抽象聚合）\u003C\u002Ftd>\n   \u003Ctd>约定\"能交出迭代器\"\u003C\u002Ftd>\n   \u003Ctd>`java.lang.Iterable`（JDK 自带）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>ConcreteAggregate（具体聚合）\u003C\u002Ftd>\n   \u003Ctd>你自己的集合类\u003C\u002Ftd>\n   \u003Ctd>Bookshelf\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>注意 \u003Ccode>Iterable\u003C\u002Fcode> 在 \u003Ccode>java.lang\u003C\u002Fcode> 包里——和 String、Object 一个包待遇。for-each 语法糖在编译期只认它：\u003Cstrong>实现了 Iterable 的类就能被 for-each，这是写进 Java 语言规范的模式\u003C\u002Fstrong>，其他二十二式谁都没有这个地位。\u003C\u002Fp>\n\u003Ch2>二、优点与缺点\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>优点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>遍历协议统一\u003C\u002Fstrong>。数组、链表、哈希表、树、数据库游标……内部结构天差地别，对外全是 hasNext\u002Fnext。调用方一套遍历代码走遍所有容器。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>不暴露内部表示\u003C\u002Fstrong>。集合内部随便重构——数组换链表、链表换数据库——外界零感知。本文书架中途扩容了数组，遍历代码一行没改。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>同一集合，多种遍历\u003C\u002Fstrong>。正序一个迭代器、倒序一个迭代器、过滤版再来一个，还能同时开几个迭代器各走各的，互不干扰。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>职责分离\u003C\u002Fstrong>。聚合管存储，迭代器管遍历，单一职责各安其位。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>缺点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>简单场景嫌绕\u003C\u002Fstrong>。就一个数组，写个迭代器类纯属仪式感。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>基础版只能单向单游标\u003C\u002Fstrong>。不能回退、不能跳到某位——ListIterator 补了双向和索引课，但那是 List 专属。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>遍历中修改集合是经典雷区\u003C\u002Fstrong>。删着元素遍历着，要么漏元素要么直接炸异常（第六节专门说）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>每次 next 一层虚调用\u003C\u002Fstrong>。现代 JIT 内联之后基本无感，但极端性能敏感的场景有人在意。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、什么时候用\u003C\u002Fh2>\n\u003Cp>判断标准：\u003Cstrong>你写了一个\"容器\"，外界需要逐个访问里面的元素，但你不想（或不该）暴露内部结构。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>自定义集合类\u003C\u002Fstrong>：自己写的树、图、跳表、环形缓冲区，想让 for-each 直接生效——本文场景\u003C\u002Fli>\n \u003Cli>\u003Cstrong>非内存容器\u003C\u002Fstrong>：数据库游标（ResultSet）、大文件逐行读取、网络流分帧——\"元素\"不在内存里堆着，只能一个一个来\u003C\u002Fli>\n \u003Cli>\u003Cstrong>同一集合多种遍历序\u003C\u002Fstrong>：正序、倒序、按权重、按时间\u003C\u002Fli>\n \u003Cli>\u003Cstrong>屏蔽实现差异\u003C\u002Fstrong>：底层今天用数组明天换 NoSQL，上层遍历代码不想跟着动\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>反面对照：用 JDK 集合直接遍历就好，别自己造轮子；一次性下标访问数组，直接来，不必抽象。\u003C\u002Fp>\n\u003Ch2>四、实现方式：让书架被 for-each\u003C\u002Fh2>\n\u003Ch3>4.1 反面教材：内部结构裸奔\u003C\u002Fh3>\n\u003Cp>先看书架不提供迭代器时，外界怎么遍历它：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class BadBookshelf {\n\n    public final Book[] books = new Book[10];   \u002F\u002F public 字段：内部结构直接裸奔\n    public int count;\n\n    public void add(Book book) {\n        books[count++] = book;\n    }\n\n    public Book[] getBooks() { return books; }\n    public int getCount() { return count; }\n    public Book getBook(int index) { return books[index]; }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>外界遍历只能这么写：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">for (int i = 0; i &lt; bad.count; i++) {\n    System.out.println(bad.books[i]);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这行代码成立的前提是外界必须知道三件事：内部是数组、下标从 0 开始、还有一个 count 字段。恶果在测试输出里看得明明白白：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">裸数组长 10，真实书 3 本——容量分配泄露给外界\n外界 raw[0] = null 后，书架第一本书：null\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>其一，\u003Cstrong>内部结构的每个细节都是负债\u003C\u002Fstrong>：数组长度 10 这种内存分配细节，居然成了外界可见的事实，哪天书架改成 8 个槽，调用方\"10\"就错了。其二，\u003Cstrong>外界拿到了就能篡改\u003C\u002Fstrong>：\u003Ccode>raw[0] = null\u003C\u002Fcode> 一行，书架第一本书直接蒸发，书架毫不知情——封装被连锅端。其三，也是最致命的：哪天书架内部从数组换成链表，全世界所有调用方的循环代码集体作废。\u003C\u002Fp>\n\u003Ch3>4.2 正解：实现 Iterable，发导游不发地图\u003C\u002Fh3>\n\u003Cp>第一步，书架实现 Iterable，内部结构全部私有化，外加自动扩容：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class Bookshelf implements Iterable&lt;Book&gt; {\n\n    private Book[] books = new Book[2];   \u002F\u002F 初始容量故意给小，演示内部扩容\n    private int count;\n\n    public void add(Book book) {\n        if (count == books.length) {\n            Book[] bigger = new Book[books.length * 2];\n            System.arraycopy(books, 0, bigger, 0, books.length);\n            books = bigger;   \u002F\u002F 内部悄悄扩容，外界毫无感知\n        }\n        books[count++] = book;\n    }\n\n    @Override\n    public Iterator&lt;Book&gt; iterator() {\n        return new BookshelfIterator();\n    }\n    \u002F\u002F 倒序迭代器见下文\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第二步，迭代器做成书架的\u003Cstrong>内部类\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">    \u002F\u002F 迭代器做成内部类：天然访问书架的私有结构，外界只见接口不见实现\n    private class BookshelfIterator implements Iterator&lt;Book&gt; {\n\n        private int cursor = 0;\n\n        @Override\n        public boolean hasNext() { return cursor &lt; count; }\n\n        @Override\n        public Book next() {\n            if (!hasNext()) throw new NoSuchElementException();\n            return books[cursor++];\n        }\n    }\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>顺手再送一个倒序迭代器，展示\"同一集合、多种遍历策略\"：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">    public Iterator&lt;Book&gt; reverseIterator() {\n        return new Iterator&lt;&gt;() {\n            private int cursor = count - 1;\n\n            @Override\n            public boolean hasNext() { return cursor &gt;= 0; }\n\n            @Override\n            public Book next() {\n                if (!hasNext()) throw new NoSuchElementException();\n                return books[cursor--];\n            }\n        };\n    }\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第三步，测试。三段遍历：手动迭代、for-each、倒序：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class IteratorTest {\n\n    public static void main(String[] args) {\n        System.out.println(\"========== 反面教材：内部结构裸奔 ==========\");\n        BadBookshelf bad = new BadBookshelf();\n        bad.add(new Book(\"设计模式\", \"GoF\"));\n        bad.add(new Book(\"重构\", \"Martin Fowler\"));\n        bad.add(new Book(\"Effective Java\", \"Joshua Bloch\"));\n\n        for (int i = 0; i &lt; bad.count; i++) {\n            System.out.println(\"  \" + bad.books[i]);\n        }\n\n        Book[] raw = bad.getBooks();\n        System.out.println(\"  裸数组长 \" + raw.length + \"，真实书 \" + bad.count + \" 本——容量分配泄露给外界\");\n        raw[0] = null;\n        System.out.println(\"  外界 raw[0] = null 后，书架第一本书：\" + bad.getBook(0));\n\n        System.out.println();\n        System.out.println(\"========== 正解：迭代器模式 ==========\");\n        Bookshelf shelf = new Bookshelf();\n        shelf.add(new Book(\"设计模式\", \"GoF\"));\n        shelf.add(new Book(\"重构\", \"Martin Fowler\"));\n        shelf.add(new Book(\"Effective Java\", \"Joshua Bloch\"));\n        shelf.add(new Book(\"代码整洁之道\", \"Robert Martin\"));\n\n        System.out.println(\"-- 手动迭代：hasNext + next --\");\n        Iterator&lt;Book&gt; it = shelf.iterator();\n        while (it.hasNext()) {\n            System.out.println(\"  \" + it.next());\n        }\n\n        System.out.println(\"-- for-each：编译器替你展开成迭代器 --\");\n        for (Book book : shelf) {\n            System.out.println(\"  \" + book);\n        }\n\n        System.out.println(\"-- 倒序迭代：同集合、另一种遍历策略 --\");\n        Iterator&lt;Book&gt; rit = shelf.reverseIterator();\n        while (rit.hasNext()) {\n            System.out.println(\"  \" + rit.next());\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行输出：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">========== 反面教材：内部结构裸奔 ==========\n  《设计模式》-GoF\n  《重构》-Martin Fowler\n  《Effective Java》-Joshua Bloch\n  裸数组长 10，真实书 3 本——容量分配泄露给外界\n  外界 raw[0] = null 后，书架第一本书：null\n\n========== 正解：迭代器模式 ==========\n-- 手动迭代：hasNext + next --\n  《设计模式》-GoF\n  《重构》-Martin Fowler\n  《Effective Java》-Joshua Bloch\n  《代码整洁之道》-Robert Martin\n-- for-each：编译器替你展开成迭代器 --\n  《设计模式》-GoF\n  《重构》-Martin Fowler\n  《Effective Java》-Joshua Bloch\n  《代码整洁之道》-Robert Martin\n-- 倒序迭代：同集合、另一种遍历策略 --\n  《代码整洁之道》-Robert Martin\n  《Effective Java》-Joshua Bloch\n  《重构》-Martin Fowler\n  《设计模式》-GoF\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>4.3 三处细节咂摸一下\u003C\u002Fh3>\n\u003Cp>\u003Cstrong>细节一：for-each 只是迭代器的语法糖。\u003C\u002Fstrong>\u003Ccode>for (Book book : shelf)\u003C\u002Fcode> 编译器会展开成：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">for (Iterator&lt;Book&gt; it = shelf.iterator(); it.hasNext(); ) {\n    Book book = it.next();\n    ...\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>javap 反编译 class 文件可以亲眼验证。所以\"实现 Iterable 就能被 for-each\"不是玄学，是编译器的一行展开——\u003Ccode>iterator()\u003C\u002Fcode> 返回的对象，就是冒号背后工作的那位。\u003C\u002Fp>\n\u003Cp>**细节二：迭代器是有状态的，状态就是游标。**BookshelfIterator 的 cursor 字段记着\"我走到第几本了\"。这也是为什么同一个书架能同时开两个迭代器各走各的——每个迭代器持有自己的游标，互不干扰。回头看 ⑱ 命令模式说\"命令有状态\"，迭代器是同款道理：\u003Cstrong>无状态的对象是工具，有状态的对象是\"一段进行中的事\"\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>**细节三：迭代器做成内部类，是 JDK 的同款选择。**java.util.ArrayList 里的 Itr 就是一个私有内部类——内部类天然访问外部类的私有字段（books、count），外界只见 Iterator 接口，不见任何实现细节。\"聚合藏结构、迭代器做内应\"，封装滴水不漏。\u003C\u002Fp>\n\u003Ch2>五、在框架里找它\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>JDK 集合框架——整个大厦的地基。\u003C\u002Fstrong>\u003Ccode>Collection extends Iterable\u003C\u002Fcode>，所以 List、Set、Queue、Deque 全员可迭代；Map 不在体系里，于是它提供了 \u003Ccode>entrySet().iterator()\u003C\u002Fcode> 的入口。你每天写的每个 for-each，都在走这条协议。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Scanner——一个会读输入的迭代器。\u003C\u002Fstrong>\u003Ccode>new Scanner(System.in)\u003C\u002Fcode> 实现的就是 \u003Ccode>Iterator&lt;String&gt;\u003C\u002Fcode>：hasNext 是\"还有输入吗\"，next 是\"读下一个\"。读文件、读网络流、分词，全是同一套协议换数据源。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>JDBC ResultSet——数据库游标的对象化。\u003C\u002Fstrong>\u003Ccode>rs.next()\u003C\u002Fcode> 把游标往前推一行，返回 false 表示扫完。一张几千万行的表，迭代器协议让你\"像遍历数组一样\"扫完它，而不需要它整个装进内存——迭代器天然适合\"元素不能一次性到齐\"的场景。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Stream 与 Spliterator——迭代器的 Java 8 进化版。\u003C\u002Fstrong>\u003Ccode>list.parallelStream()\u003C\u002Fcode> 背后是 Spliterator（可拆分迭代器）：把集合切成几段分给多线程并行处理。老 Iterator 只能一个人顺序走，Spliterator 能\"分身\"——这是遍历协议对多核时代的回应。\u003C\u002Fp>\n\u003Cp>一句话总结这一节：\u003Cstrong>凡是要\"一个一个拿出来\"的地方，协议全是 hasNext\u002Fnext，迭代器是数据容器世界的普通话。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch2>六、扩展：fail-fast——遍历中修改集合怎么办\u003C\u002Fh2>\n\u003Cp>基本版迭代器有一个绕不开的坑：**遍历到一半，集合被改了怎么办？**游标指着第 3 本，有人把第 3 本删了——轻则漏元素、重则死循环。\u003C\u002Fp>\n\u003Cp>JDK 集合的答案是 fail-fast（快速失败）：每个集合维护一个版本号 modCount，迭代器创建时记住当时的版本，每次 next 检查版本是否还一致——不一致立刻抛 \u003Ccode>ConcurrentModificationException\u003C\u002Fcode>。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">for (Book book : shelf) {\n    shelf.add(new Book(\"新书\", \"谁\"));   \u002F\u002F modCount 变了\n    System.out.println(book);            \u002F\u002F 下一次 next() 直接抛异常\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>设计哲学值得咂摸：**遍历中的修改无法安全处理时，宁可立刻炸给你看，也不悄悄给出错误结果。**快速失败是诚实，静默出错才是灾难。（顺带一提，删除自己刚访问的元素是唯一合法操作：\u003Ccode>it.remove()\u003C\u002Fcode>，迭代器会同步修正游标。）\u003C\u002Fp>\n\u003Cp>也有反着来的 fail-safe 策略：\u003Ccode>CopyOnWriteArrayList\u003C\u002Fcode>（⑯ 观察者篇提过它的兄弟思路）遍历时拿的是当时的快照，遍历期间的修改不影响本次遍历——用\"数据可能略旧\"换\"绝不抛异常\"。没有银弹，只有取舍，两个流派各自服侍各自的场景。\u003C\u002Fp>\n\u003Ch2>写在最后\u003C\u002Fh2>\n\u003Cp>迭代器模式一句话总结：\u003Cstrong>聚合藏起内部结构，只派发导游；hasNext\u002Fnext 两个问题统一天下遍历，for-each 不过是它的语法糖。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>下一篇 ㉑ 备忘录模式——⑱ 命令模式讲撤销时，我们靠\"逆操作\"轻装回滚。但有些对象没法描述逆操作：游戏角色的血蓝金币要存档、IDE 要恢复整个工作区状态——难道把所有字段全公开出去让外界抄一份？\u003Cstrong>不破坏封装的前提下，把内部状态存下来、再原样恢复\u003C\u002Fstrong>——命令模式管\"怎么撤销\"，备忘录管\"怎么存档\"。备忘录模式，下一篇见。\u003C\u002Fp>",[],"index,follow",null,{"code":4,"message":5,"data":44},[45,54,63],{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":52,"publishedAt":53},"2113931511261564929","design-pattern-08","设计模式修炼之路 ⑧ 过滤器模式——与或非条件当积木","非 GoF、与或非条件当积木、Predicate 是 JDK 收编版",3801,7442,19,"2024-01-08T20:51:45",{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":61,"publishedAt":62},"2113932970753527809","design-pattern-13","设计模式修炼之路 ⑬ 代理模式——控制访问，而非增强","控制访问而非增强、JDK 动态代理的 $Proxy0、CGLIB 与 Spring AOP 的老搭档",3804,9153,23,"2024-05-02T10:29:24",{"id":64,"slug":65,"title":66,"summary":67,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":68,"likeCount":4,"wordCount":69,"readingMinutes":70,"publishedAt":71},"2113934492581236737","design-pattern-24","设计模式修炼之路 ㉔ 访问者模式——结构与操作分离","双分派、结构与操作分离、visit(this) 不能省、FileVisitor 与 ASM",2450,5892,15,"2024-12-12T14:45:37",{"code":4,"message":5,"data":73},{"prev":74,"next":82},{"id":75,"slug":76,"title":77,"summary":78,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":79,"likeCount":4,"wordCount":80,"readingMinutes":36,"publishedAt":81},"2113933842262790145","design-pattern-19","设计模式修炼之路 ⑲ 状态模式——状态驱动行为","状态驱动行为、流转规则住进状态家、Thread 状态机、状态 vs 策略终局对决",1320,6715,"2024-09-02T16:09:32",{"id":83,"slug":84,"title":85,"summary":86,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":87,"likeCount":4,"wordCount":88,"readingMinutes":36,"publishedAt":89},"2113934123146940417","design-pattern-21","设计模式修炼之路 ㉑ 备忘录模式——不破坏封装的存档术","不破坏封装的存档术、宽窄接口、Git 与 Savepoint、命令+备忘录合订",3711,6474,"2024-10-08T16:37:17",{"code":4,"message":5,"data":91},[92,98,103,109],{"id":93,"name":94,"slug":95,"description":96,"sortOrder":4,"articleCount":97},"1946000000000000001","antlr4","tech","编程与技术笔记","13",{"id":32,"name":99,"slug":100,"description":101,"sortOrder":4,"articleCount":102},"设计模式","design-pattern","设计模式系列","34",{"id":104,"name":105,"slug":106,"description":107,"sortOrder":33,"articleCount":108},"1946000000000000002","随笔","essay","生活与思考","0",{"id":110,"name":111,"slug":112,"description":113,"sortOrder":114,"articleCount":115},"2105200093764698113","工具","tools","好用的工具推荐",2,"1",{"html":117,"toc":118},"\u003Cblockquote>\n \u003Cp>行为型第七站。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>上一篇结尾问了个问题：你每天都在写 \u003Ccode>for (String s : list)\u003C\u002Fcode>，这个冒号背后是谁在工作？为什么 ArrayList、HashSet、LinkedList 长得八竿子打不着，却都能被同一套 for-each 遍历？\u003C\u002Fp>\n\u003Cp>答案是 GoF 二十三式里唯一一个被 JDK \u003Cstrong>语言级收编\u003C\u002Fstrong>的模式——迭代器。这一篇就手写一个能被 for-each 的自定义集合，把遍历协议这件事彻底讲透。\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、迭代器模式是什么\u003C\u002Fh2>\n\u003Cp>GoF 的原始定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation. （提供一种方法顺序访问一个聚合对象中的各个元素，而又不暴露它的内部表示。）\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>遍历这件事，集合自己最清楚。与其把内部结构摊开给外界看，不如集合派一个\"导游\"出去——导游只回答两个问题：还有下一个吗？带我去下一个。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>四个角色，但有两个 JDK 已经替你写好了：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>角色\u003C\u002Fth>\n   \u003Cth>干什么\u003C\u002Fth>\n   \u003Cth>本文对应\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Iterator（抽象迭代器）\u003C\u002Ftd>\n   \u003Ctd>统一遍历协议：hasNext \u002F next\u003C\u002Ftd>\n   \u003Ctd>`java.util.Iterator`（JDK 自带）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>ConcreteIterator（具体迭代器）\u003C\u002Ftd>\n   \u003Ctd>记住游标走到哪，具体怎么走\u003C\u002Ftd>\n   \u003Ctd>BookshelfIterator \u002F 倒序迭代器\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Aggregate（抽象聚合）\u003C\u002Ftd>\n   \u003Ctd>约定\"能交出迭代器\"\u003C\u002Ftd>\n   \u003Ctd>`java.lang.Iterable`（JDK 自带）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>ConcreteAggregate（具体聚合）\u003C\u002Ftd>\n   \u003Ctd>你自己的集合类\u003C\u002Ftd>\n   \u003Ctd>Bookshelf\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>注意 \u003Ccode>Iterable\u003C\u002Fcode> 在 \u003Ccode>java.lang\u003C\u002Fcode> 包里——和 String、Object 一个包待遇。for-each 语法糖在编译期只认它：\u003Cstrong>实现了 Iterable 的类就能被 for-each，这是写进 Java 语言规范的模式\u003C\u002Fstrong>，其他二十二式谁都没有这个地位。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、优点与缺点\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>优点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>遍历协议统一\u003C\u002Fstrong>。数组、链表、哈希表、树、数据库游标……内部结构天差地别，对外全是 hasNext\u002Fnext。调用方一套遍历代码走遍所有容器。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>不暴露内部表示\u003C\u002Fstrong>。集合内部随便重构——数组换链表、链表换数据库——外界零感知。本文书架中途扩容了数组，遍历代码一行没改。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>同一集合，多种遍历\u003C\u002Fstrong>。正序一个迭代器、倒序一个迭代器、过滤版再来一个，还能同时开几个迭代器各走各的，互不干扰。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>职责分离\u003C\u002Fstrong>。聚合管存储，迭代器管遍历，单一职责各安其位。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>缺点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>简单场景嫌绕\u003C\u002Fstrong>。就一个数组，写个迭代器类纯属仪式感。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>基础版只能单向单游标\u003C\u002Fstrong>。不能回退、不能跳到某位——ListIterator 补了双向和索引课，但那是 List 专属。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>遍历中修改集合是经典雷区\u003C\u002Fstrong>。删着元素遍历着，要么漏元素要么直接炸异常（第六节专门说）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>每次 next 一层虚调用\u003C\u002Fstrong>。现代 JIT 内联之后基本无感，但极端性能敏感的场景有人在意。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-3\">三、什么时候用\u003C\u002Fh2>\n\u003Cp>判断标准：\u003Cstrong>你写了一个\"容器\"，外界需要逐个访问里面的元素，但你不想（或不该）暴露内部结构。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>自定义集合类\u003C\u002Fstrong>：自己写的树、图、跳表、环形缓冲区，想让 for-each 直接生效——本文场景\u003C\u002Fli>\n \u003Cli>\u003Cstrong>非内存容器\u003C\u002Fstrong>：数据库游标（ResultSet）、大文件逐行读取、网络流分帧——\"元素\"不在内存里堆着，只能一个一个来\u003C\u002Fli>\n \u003Cli>\u003Cstrong>同一集合多种遍历序\u003C\u002Fstrong>：正序、倒序、按权重、按时间\u003C\u002Fli>\n \u003Cli>\u003Cstrong>屏蔽实现差异\u003C\u002Fstrong>：底层今天用数组明天换 NoSQL，上层遍历代码不想跟着动\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>反面对照：用 JDK 集合直接遍历就好，别自己造轮子；一次性下标访问数组，直接来，不必抽象。\u003C\u002Fp>\n\u003Ch2 id=\"toc-4\">四、实现方式：让书架被 for-each\u003C\u002Fh2>\n\u003Ch3 id=\"toc-5\">4.1 反面教材：内部结构裸奔\u003C\u002Fh3>\n\u003Cp>先看书架不提供迭代器时，外界怎么遍历它：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadBookshelf\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] books \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">10\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">];   \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F public 字段：内部结构直接裸奔\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> count;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Book \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        books[count\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">++\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">] \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> book;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getBooks\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() { \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> books; }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> getCount\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() { \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> count; }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Book \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getBook\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> index\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) { \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> books[index]; }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>外界遍历只能这么写：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">; i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.count; i\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">++\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(bad.books[i]);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这行代码成立的前提是外界必须知道三件事：内部是数组、下标从 0 开始、还有一个 count 字段。恶果在测试输出里看得明明白白：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>裸数组长 10，真实书 3 本——容量分配泄露给外界\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>外界 raw[0] = null 后，书架第一本书：null\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>其一，\u003Cstrong>内部结构的每个细节都是负债\u003C\u002Fstrong>：数组长度 10 这种内存分配细节，居然成了外界可见的事实，哪天书架改成 8 个槽，调用方\"10\"就错了。其二，\u003Cstrong>外界拿到了就能篡改\u003C\u002Fstrong>：\u003Ccode>raw[0] = null\u003C\u002Fcode> 一行，书架第一本书直接蒸发，书架毫不知情——封装被连锅端。其三，也是最致命的：哪天书架内部从数组换成链表，全世界所有调用方的循环代码集体作废。\u003C\u002Fp>\n\u003Ch3 id=\"toc-6\">4.2 正解：实现 Iterable，发导游不发地图\u003C\u002Fh3>\n\u003Cp>第一步，书架实现 Iterable，内部结构全部私有化，外加自动扩容：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Bookshelf\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Iterable\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] books \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">2\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">];   \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 初始容量故意给小，演示内部扩容\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> count;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Book \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (count \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> books.length) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] bigger \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[books.length \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">*\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 2\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">];\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">arraycopy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(books, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, bigger, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, books.length);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            books \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bigger;   \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 内部悄悄扩容，外界毫无感知\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        books[count\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">++\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">] \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> book;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Iterator&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">iterator\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BookshelfIterator\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 倒序迭代器见下文\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第二步，迭代器做成书架的\u003Cstrong>内部类\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 迭代器做成内部类：天然访问书架的私有结构，外界只见接口不见实现\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BookshelfIterator\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Iterator\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> cursor \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> boolean\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> hasNext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() { \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> cursor \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> count; }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Book \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">next\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">hasNext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()) \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">throw\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> NoSuchElementException\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> books[cursor\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">++\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">];\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>顺手再送一个倒序迭代器，展示\"同一集合、多种遍历策略\"：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Iterator\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">Book\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">>\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> reverseIterator\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Iterator&#x3C;>() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> cursor \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> count \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> boolean\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> hasNext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() { \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> cursor \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">>=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">; }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Book \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">next\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">                if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">hasNext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()) \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">throw\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> NoSuchElementException\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">                return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> books[cursor\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">--\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">];\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        };\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第三步，测试。三段遍历：手动迭代、for-each、倒序：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> IteratorTest\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> main\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">String\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">args\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"========== 反面教材：内部结构裸奔 ==========\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        BadBookshelf bad \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadBookshelf\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"设计模式\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"GoF\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"重构\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Martin Fowler\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Effective Java\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Joshua Bloch\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">; i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.count; i\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">++\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.books[i]);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] raw \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getBooks\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  裸数组长 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> raw.length \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"，真实书 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.count \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 本——容量分配泄露给外界\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        raw[\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">] \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  外界 raw[0] = null 后，书架第一本书：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getBook\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"========== 正解：迭代器模式 ==========\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        Bookshelf shelf \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Bookshelf\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        shelf.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"设计模式\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"GoF\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        shelf.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"重构\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Martin Fowler\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        shelf.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Effective Java\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Joshua Bloch\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        shelf.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"代码整洁之道\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Robert Martin\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"-- 手动迭代：hasNext + next --\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        Iterator&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> it \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> shelf.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">iterator\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        while\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (it.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">hasNext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> it.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">next\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"-- for-each：编译器替你展开成迭代器 --\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (Book book \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> shelf) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> book);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"-- 倒序迭代：同集合、另一种遍历策略 --\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        Iterator&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> rit \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> shelf.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">reverseIterator\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        while\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (rit.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">hasNext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> rit.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">next\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行输出：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>========== 反面教材：内部结构裸奔 ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《设计模式》-GoF\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《重构》-Martin Fowler\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《Effective Java》-Joshua Bloch\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  裸数组长 10，真实书 3 本——容量分配泄露给外界\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  外界 raw[0] = null 后，书架第一本书：null\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>========== 正解：迭代器模式 ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>-- 手动迭代：hasNext + next --\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《设计模式》-GoF\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《重构》-Martin Fowler\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《Effective Java》-Joshua Bloch\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《代码整洁之道》-Robert Martin\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>-- for-each：编译器替你展开成迭代器 --\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《设计模式》-GoF\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《重构》-Martin Fowler\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《Effective Java》-Joshua Bloch\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《代码整洁之道》-Robert Martin\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>-- 倒序迭代：同集合、另一种遍历策略 --\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《代码整洁之道》-Robert Martin\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《Effective Java》-Joshua Bloch\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《重构》-Martin Fowler\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  《设计模式》-GoF\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"toc-7\">4.3 三处细节咂摸一下\u003C\u002Fh3>\n\u003Cp>\u003Cstrong>细节一：for-each 只是迭代器的语法糖。\u003C\u002Fstrong>\u003Ccode>for (Book book : shelf)\u003C\u002Fcode> 编译器会展开成：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (Iterator&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> it \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> shelf.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">iterator\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(); it.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">hasNext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(); ) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Book book \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> it.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">next\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    ...\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>javap 反编译 class 文件可以亲眼验证。所以\"实现 Iterable 就能被 for-each\"不是玄学，是编译器的一行展开——\u003Ccode>iterator()\u003C\u002Fcode> 返回的对象，就是冒号背后工作的那位。\u003C\u002Fp>\n\u003Cp>**细节二：迭代器是有状态的，状态就是游标。**BookshelfIterator 的 cursor 字段记着\"我走到第几本了\"。这也是为什么同一个书架能同时开两个迭代器各走各的——每个迭代器持有自己的游标，互不干扰。回头看 ⑱ 命令模式说\"命令有状态\"，迭代器是同款道理：\u003Cstrong>无状态的对象是工具，有状态的对象是\"一段进行中的事\"\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>**细节三：迭代器做成内部类，是 JDK 的同款选择。**java.util.ArrayList 里的 Itr 就是一个私有内部类——内部类天然访问外部类的私有字段（books、count），外界只见 Iterator 接口，不见任何实现细节。\"聚合藏结构、迭代器做内应\"，封装滴水不漏。\u003C\u002Fp>\n\u003Ch2 id=\"toc-8\">五、在框架里找它\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>JDK 集合框架——整个大厦的地基。\u003C\u002Fstrong>\u003Ccode>Collection extends Iterable\u003C\u002Fcode>，所以 List、Set、Queue、Deque 全员可迭代；Map 不在体系里，于是它提供了 \u003Ccode>entrySet().iterator()\u003C\u002Fcode> 的入口。你每天写的每个 for-each，都在走这条协议。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Scanner——一个会读输入的迭代器。\u003C\u002Fstrong>\u003Ccode>new Scanner(System.in)\u003C\u002Fcode> 实现的就是 \u003Ccode>Iterator&lt;String&gt;\u003C\u002Fcode>：hasNext 是\"还有输入吗\"，next 是\"读下一个\"。读文件、读网络流、分词，全是同一套协议换数据源。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>JDBC ResultSet——数据库游标的对象化。\u003C\u002Fstrong>\u003Ccode>rs.next()\u003C\u002Fcode> 把游标往前推一行，返回 false 表示扫完。一张几千万行的表，迭代器协议让你\"像遍历数组一样\"扫完它，而不需要它整个装进内存——迭代器天然适合\"元素不能一次性到齐\"的场景。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Stream 与 Spliterator——迭代器的 Java 8 进化版。\u003C\u002Fstrong>\u003Ccode>list.parallelStream()\u003C\u002Fcode> 背后是 Spliterator（可拆分迭代器）：把集合切成几段分给多线程并行处理。老 Iterator 只能一个人顺序走，Spliterator 能\"分身\"——这是遍历协议对多核时代的回应。\u003C\u002Fp>\n\u003Cp>一句话总结这一节：\u003Cstrong>凡是要\"一个一个拿出来\"的地方，协议全是 hasNext\u002Fnext，迭代器是数据容器世界的普通话。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch2 id=\"toc-9\">六、扩展：fail-fast——遍历中修改集合怎么办\u003C\u002Fh2>\n\u003Cp>基本版迭代器有一个绕不开的坑：**遍历到一半，集合被改了怎么办？**游标指着第 3 本，有人把第 3 本删了——轻则漏元素、重则死循环。\u003C\u002Fp>\n\u003Cp>JDK 集合的答案是 fail-fast（快速失败）：每个集合维护一个版本号 modCount，迭代器创建时记住当时的版本，每次 next 检查版本是否还一致——不一致立刻抛 \u003Ccode>ConcurrentModificationException\u003C\u002Fcode>。\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (Book book \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> shelf) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    shelf.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Book\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"新书\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"谁\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));   \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F modCount 变了\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(book);            \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 下一次 next() 直接抛异常\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>设计哲学值得咂摸：**遍历中的修改无法安全处理时，宁可立刻炸给你看，也不悄悄给出错误结果。**快速失败是诚实，静默出错才是灾难。（顺带一提，删除自己刚访问的元素是唯一合法操作：\u003Ccode>it.remove()\u003C\u002Fcode>，迭代器会同步修正游标。）\u003C\u002Fp>\n\u003Cp>也有反着来的 fail-safe 策略：\u003Ccode>CopyOnWriteArrayList\u003C\u002Fcode>（⑯ 观察者篇提过它的兄弟思路）遍历时拿的是当时的快照，遍历期间的修改不影响本次遍历——用\"数据可能略旧\"换\"绝不抛异常\"。没有银弹，只有取舍，两个流派各自服侍各自的场景。\u003C\u002Fp>\n\u003Ch2 id=\"toc-10\">写在最后\u003C\u002Fh2>\n\u003Cp>迭代器模式一句话总结：\u003Cstrong>聚合藏起内部结构，只派发导游；hasNext\u002Fnext 两个问题统一天下遍历，for-each 不过是它的语法糖。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>下一篇 ㉑ 备忘录模式——⑱ 命令模式讲撤销时，我们靠\"逆操作\"轻装回滚。但有些对象没法描述逆操作：游戏角色的血蓝金币要存档、IDE 要恢复整个工作区状态——难道把所有字段全公开出去让外界抄一份？\u003Cstrong>不破坏封装的前提下，把内部状态存下来、再原样恢复\u003C\u002Fstrong>——命令模式管\"怎么撤销\"，备忘录管\"怎么存档\"。备忘录模式，下一篇见。\u003C\u002Fp>",[119,122,125,128,131,135,138,141,144,147],{"id":120,"text":121,"level":114},"toc-1","一、迭代器模式是什么",{"id":123,"text":124,"level":114},"toc-2","二、优点与缺点",{"id":126,"text":127,"level":114},"toc-3","三、什么时候用",{"id":129,"text":130,"level":114},"toc-4","四、实现方式：让书架被 for-each",{"id":132,"text":133,"level":134},"toc-5","4.1 反面教材：内部结构裸奔",3,{"id":136,"text":137,"level":134},"toc-6","4.2 正解：实现 Iterable，发导游不发地图",{"id":139,"text":140,"level":134},"toc-7","4.3 三处细节咂摸一下",{"id":142,"text":143,"level":114},"toc-8","五、在框架里找它",{"id":145,"text":146,"level":114},"toc-9","六、扩展：fail-fast——遍历中修改集合怎么办",{"id":148,"text":149,"level":114},"toc-10","写在最后"]