小白学Java:迭代器原来是这么回事

前文传送门: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这个实现类中,定义了两个指针:cursorlastRet。(还有个属性为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编程思想》

小白学Java:迭代器原来是这么回事的更多相关文章

  1. 小白学Java:奇怪的RandomAccess

    目录 小白学Java:奇怪的RandomAccess RandomAccess是个啥 forLoop与Iterator的区别 判断是否为RandomAccess 小白学Java:奇怪的RandomAc ...

  2. 小白学Java:内部类

    目录 小白学Java:内部类 内部类的分类 成员内部类 局部内部类 静态内部类 匿名内部类 内部类的继承 内部类有啥用 小白学Java:内部类 内部类是封装的一种形式,是定义在类或接口中的类. 内部类 ...

  3. 小白学Java:I/O流

    目录 小白学Java:I/O流 基本分类 发展史 文件字符流 输出的基本结构 流中的异常处理 异常处理新方式 读取的基本结构 运用输入与输出 文件字节流 缓冲流 字符缓冲流 装饰设计模式 转换流(适配 ...

  4. 小白学Java:包装类

    目录 小白学Java:包装类 包装类的继承关系 创建包装类实例 自动装箱与拆箱 自动装箱 自动拆箱 包装类型的比较 "=="比较 equals比较 自动装箱与拆箱引发的弊端 自动装 ...

  5. 小白学Java:老师!泛型我懂了!

    目录 小白学Java:老师!泛型我懂了! 泛型概述 定义泛型 泛型类的定义 泛型方法的定义 类型变量的限定 原生类型与向后兼容 通配泛型 非受限通配 受限通配 下限通配 泛型的擦除和限制 类型擦除 类 ...

  6. 小白学Java:File类

    目录 小白学Java:File类 不同风格的分隔符 绝对与相对路径 File类常用方法 常用构造器 创建方法 判断方法 获取方法 命名方法 删除方法 小白学Java:File类 我们可以知道,存储在程 ...

  7. 小白学Java:RandomAccessFile

    目录 小白学Java:RandomAccessFile 概述 继承与实现 构造器 模式设置 文件指针 操作数据 读取数据 read(byte b[])与read() 追加数据 插入数据 小白学Java ...

  8. 【aliyun】学java,看这里,不迷茫!1460道Java热门问题

    阿里极客公益活动: 或许你挑灯夜战只为一道难题 或许你百思不解只求一个答案 或许你绞尽脑汁只因一种未知 那么他们来了,阿里系技术专家来云栖问答为你解答技术难题了 他们用户自己手中的技术来帮助用户成长 ...

  9. 浅谈Java迭代器

    迭代器Iterator 概述: 迭代器(Iterator):它不是一个容器,它是一种用于访问容器的方法,可用于迭代 List.Set和Map等容器. 迭代:一个一个的往外拿. 作用:帮我们遍历或者拿到 ...

随机推荐

  1. win10 uwp 在 Canvas 放一个超过大小的元素会不会被裁剪

    我尝试在一个宽度200高度200的 Canvas 放了一个宽度 300 高度 300 的元素,这个元素会不会被 Canvas 裁剪了? 经过我的测试,发现默认是不会被裁剪 火火问了我一个问题,如果有一 ...

  2. window 系统下修改`CMD`的编码格式的方法,`CHCP` 的 使用

    CHCP的使用 CHCP是一个计算机指令,能够显示或设置活动代码页编号. 一般上是在命令提示框中使用,用来查询和修改命令提示框的编码格式 具体使用方法 查看活动代码页编号 方式1: >>& ...

  3. linux模块参数

    驱动需要知道的几个参数因不同的系统而不同. 从使用的设备号( 如我们在下一章见到的 ) 到驱动应当任何操作的几个方面. 例如, SCSI 适配器的驱动常常有选项控制标记命令队列 的使用, IDE 驱动 ...

  4. get_free_page 和其友

    如果一个模块需要分配大块的内存, 它常常最好是使用一个面向页的技术. 请求整个页也 有其他的优点, 这个在 15 章介绍. 为分配页, 下列函数可用: get_zeroed_page(unsigned ...

  5. 2018.11.16 浪在ACM 集训队第五次测试赛

    2018.11.16 浪在ACM 集训队第五次测试赛 整理人:李继朋 Problem A : 参考博客:[1]朱远迪 Problem B : 参考博客: Problem C : 参考博客:[1]马鸿儒 ...

  6. 文本框(代替input)输入长度限制、提示

    <div class="inform_content_text"> <textarea name="name" placeholder=&qu ...

  7. springmvc 参数校验/aop失效/@PathVariable 参数为空

    添加依赖 <!-- 参数校验 --> <dependency> <groupId>org.hibernate.validator</groupId> & ...

  8. 【Docker】镜像分层存储与镜像精简

    Linux操作系统 Linux操作系统由内核空间和用户空间组成. 内核空间是kernel,用户空间是rootfs, 不同Linux发行版的区别主要是rootfs.比如 Ubuntu 14.04 使用 ...

  9. 错误 137 (net::ERR_NAME_RESOLUTION_FAILED):未知错误

    上午装了一个软件(APMServ 5.2.6) 1.点击启动Apache不成功,mysql却成功了.后来知晓是80端口冲突的问题 2.当时却不知道,就鬼使神差的   点击了  边上的  解决冲突问题 ...

  10. python代码规范以及函数注释规范

    摘要 本文给出主Python版本标准库的编码约定.CPython的C代码风格参见​PEP7.本文和​PEP 257 文档字符串标准改编自Guido最初的<Python Style Guide&g ...