目录
迭代器概述
迭代器设计模式
Iterator 定义的方法
迭代器: 统一方式
Iterator 的总结
前文传送门: Enumeration
上一篇, 我们谈到了那个古老的迭代器 Enumeration, 还谈到了取代他的新迭代器 --Iterator. 相比于以往, 这个新物种又有哪些优点呢?
迭代器这个词, 在没查找许多资料之前, 我只知道个大概, 我知道它可以用来遍历集合, 但是至于它其中的奥妙, 并没有做深究. 本篇文章关于 Iterator 迭代器做了小小的总结, 巩固学习, 如果有理解错误, 或叙述不当之处, 还望大家评论区批评指针.
迭代器概述
官方文档对 Iterator 的解释是:
它取代了 Enumeration.
它作用于任何一个 Collection.
它增加了 remove 的功能.
它优化了方法命名.
不行不行, 这描述也太简略了, 我继续查找资料:
迭代器本身是个对象, 创建迭代器的代价很小, 通常被称为轻量级对象.
迭代器其实也是一种设计模式, 它提供了一种方法顺序访问一个聚合对象中的各个元素, 但又不暴露该对象的内部表示.
迭代器设计模式
针对以上种种, 我充满了好奇, 于是在复杂的继承关系里画了又画, 最终才渐渐理清集合中所谓迭代器模式的体现, 暂时以 ArrayList 为例:
定义了一个迭代器的接口 Iterator, 里面定义了迭代器的功能, 但并没有提供实现.
定义了一个聚集接口 Iterable, 里面的 iterator() 抽象方法, 表明返回一个针对类型 T 的迭代器. 此时将 Iterable 和 Iterator 联系了起来.
我们知道聚集接口被许多接口所扩展, 定义相同的方法, Collection 接口就是其一, 所以说, 迭代器针对于所有集合都有效.
我们以集合的具体类 ArrayList 为例, 暂时忽略之间的继承关系, ArrayList 显然提供了抽象方法 iterator() 的具体实现, 我们查看源码发现, 它的返回值是一个 Itr 对象.
这个 Itr 其实是 ArrayList 的一个内部类, 它提供了迭代器接口的具体实现 (当然不一定是内部类), 这样所有东西都联系在了一起.
Iterator 定义的方法
hasNext():boolean 判断下一个元素还有没有, 有就是 true.
next(): E 返回序列中的下一个元素.
通过查看源码, 我发现, 在这个 Itr 这个实现类中, 定义了两个指针: cursor 和 lastRet.(还有个属性为 expectedModCount 初始化为 ArrayList 的版本号 modCount, 这部分与 fail-fast 机制相关, 之后会再提) 而 cursor 初始为 0, 与专门用来和集合元素数目 size 做比较的. 而 lastRet 初始化为 - 1, 如果成功执行 next 操作, 将会加 1 变成 0, 也就是上面说的 "下一个元素" 可想而知, 可以把 lastRet 认为是初始化为第一个元素之前的指针, 和将要返回的值的索引相同, 这样会好记一些.
除了上面两个方法, JDK1.8 新增了两个方法, 也是体现处它与老迭代器不同的新优势: 支持了删除操作.
remove():void 将新近返回的元素删除.
需要注意的是: remove 方法没有新近返回的元素, 也就是说 lastRet<0, 会抛出异常. 如果移除成功, 让 cursor 往回退一格, lastRet 重置为 - 1.
forEachRemaining(Consumer<? super E> consumer):void 这个是 JDK1.8 中 Iterator 新增的默认方法: 对剩余的元素执行指定的操作.
可能不太好理解: 我们通过测试来说明一下:
- List<Integer> list = new ArrayList<>();
- list.add(1);
- list.add(2);
- list.add(3);
- // 创建一个 Iterator 对象
- Iterator<Integer> it = list.iterator();
- // 返回第一个值
- System.out.print(it.next()+" ");
测试结果很明显, 只输出了第一个元素: 1.
我们继续在原代码的基础上我们的新方法:
- it.forEachRemaining(new Consumer<Integer>() {
- @Override
- public void accept(Integer integer) {
- System.out.print(integer+" ");
- }
- });
测试结果为: 1 2 3, 在原来的基础上, 把剩下的元素都打印了出来. 而这个新增的方法, 其实和我们熟悉的这个是一样的:
- while(it.hasNext()){
- System.out.print(it.next()+" ");
- }
值得一提的是: 我们之前学习的增强 for 循环, 在底层其实就是运用了 Iterator, 我通过 IDE 的 debug 调试功能, 发现在调用运行到增强 for 循环时, 自动调用了集合的 iterator() 方法, 返回了一个 Iterator 的实现类实例.
迭代器: 统一方式
通过对 Iterator 中定义方法的学习, 我们大概知道了迭代器的用途, 就是从前向后一个一个遍历元素, 而无视其内部结构. 欸, 遍历我都懂, 可无视结构在哪里体现啊? 别急, 下面来看一个例子, 让我们无视两个不同集合的结构:
首先我们定义一个方法, 它可以接收一个迭代器对象:
- public static void display(Iterator<?> T){
- while(T.hasNext()){
- System.out.print(T.next());
- }
- }
然后我们创建两个不一样的集合, 一个是 ArrayList, 一个是 HashSet, 本身是无序的, 我们接下来应该会做相应的源码学习.
- //ArrayList 有序
- List<String> list = new LinkedList<>();
- list.add("天");
- list.add("乔");
- list.add("巴");
- list.add("夏");
- //HashSet 无序
- Set<Integer> set = new HashSet<>();
- set.add(11);
- set.add(22);
- set.add(33);
- set.add(44);
- display(list.iterator());// 天 乔 巴 夏
- System.out.println();
- display(set.iterator());//33 22 11 44
可以看出来, 两个不同集合的迭代器传入 display 方法之后, 都能用一种相同的方式访问集合中的元素.
通过上面的一顿分析, 我们可以确定, 迭代器这玩意儿, 统一了访问容器的方式.
Iterator 的总结
Iterator 支持从前向后顺次遍历, 统一了对不同集合里元素的操作.
还在 Enumeration 的基础上, 简化了命名, 而且 Enumeration 并不是对所有集合都适用.
四大技能增删改查, 虽然支持删和查, 但不支持增和改.
只支持单向迭代, 某些情况下不是很灵活.(ListIterator 可以支持双向, 但只支持 List 类型)
最后, 关于迭代器, 还有一部分内容, 在日后会做总结.
参考资料:《大话设计模式》,《Java 编程思想》
来源: https://www.cnblogs.com/summerday152/p/12210030.html