Java中的Iterable与Iterator详解
在Java中,我们可以对List集合进行如下几种方式的遍历:
List<Integer> list = new ArrayList<>();
list.add(5);
list.add(23);
list.add(42);
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + ",");
}
Iterator it = list.iterator();
while (it.hasNext()) {
System.out.print(it.next() + ",");
}
for (Integer i : list) {
System.out.print(i + ",");
}
第一种就是普通的for循环,第二种为迭代器遍历,第三种是for each循环。后面两种方式涉及到Java中的iterator和iterable对象,接下来我们来看看这两个对象的区别以及如何在自定义类中实现for each循环。
Iterator与Iterable
iterator为Java中的迭代器对象,是能够对List这样的集合进行迭代遍历的底层依赖。而iterable接口里定义了返回iterator的方法,相当于对iterator的封装,同时实现了iterable接口的类可以支持for each循环。
iterator内部细节
jdk中Iterator接口主要方法如下:
public interface Iterator<E> {
boolean hasNext();
E next();
}
iterator通过以上两个方法定义了对集合迭代访问的方法,而具体的实现方式依赖于不同的实现类,具体的集合类实现Iterator接口中的方法以实现迭代。
可以发现,在List中并没有实现Iterator接口,而是实现的Iterable接口。进一步观察Iterable接口的源码可以发现其只是返回了一个Iterator对象。
public interface Iterable<T> {
Iterator<T> iterator();
}
所以我们可以使用如下方式来对List进行迭代了(通过调用iterator()方法)
Iterator it = list.iterator();
while (it.hasNext()) {
System.out.print(it.next() + ",");
}
同时实现了Iterable接口的还可以使用for each循环。
for each原理
其实for each循环内部也是依赖于Iterator迭代器,只不过Java提供的语法糖,Java编译器会将其转化为Iterator迭代器方式遍历。我们对以下for each循环进行反编译:
for (Integer i : list) {
System.out.println(i);
}
反编译后:
Integer i;
for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(i)){
i = (Integer)iterator.next();
}
可以看到Java的for each增强循环是通过iterator迭代器方式实现的。
深入探讨Iterable与Iterator关系
有一个问题,为什么不直接将hasNext(),next()方法放在Iterable接口中,其他类直接实现就可以了?
原因是有些集合类可能不止一种遍历方式,实现了Iterable的类可以再实现多个Iterator内部类,例如LinkedList中的ListItr和DescendingIterator两个内部类,就分别实现了双向遍历和逆序遍历。通过返回不同的Iterator实现不同的遍历方式,这样更加灵活。如果把两个接口合并,就没法返回不同的Iterator实现类了。ListItr相关源码如下:
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
private class ListItr implements ListIterator<E> {
...
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
...
如上所示可以通过调用list.listIterator()方法返回iterator迭代器(list.iterator()只是其默认实现)
DescendingIterator源码如下:
public Iterator<E> descendingIterator() {
return new DescendingIterator();
}
private class DescendingIterator implements Iterator<E> {
private final ListItr itr = new ListItr(size());
public boolean hasNext() {
return itr.hasPrevious();
}
public E next() {
return itr.previous();
}
public void remove() {
itr.remove();
}
}
同样可以通过list.descendingIterator()使用该迭代器。
实现自己的迭代器
我们现在有一个自定义类ArrayMap,现在如果对其进行如下for each遍历:
ArrayMap<String, Integer> am = new ArrayMap<>();
am.put("hello", 5);
am.put("syrups", 10);
for (String s: am) {
System.out.println(s);
}
由于我们并没有实现hashNext和next抽象方法,所以无法对其进行遍历。
自定义迭代器类
我们首先自定义一个迭代器类实现hashNext和next方法,并将其作为ArrayMap的内部类,相关代码如下:
public class KeyIterator implements Iterator<K> {
private int ptr;
public KeyIterator() {
ptr = 0;
}
@Override
public boolean hasNext() {
return (ptr != size);
}
@Override
public K next() {
K returnItem = keys[ptr];
ptr += 1;
return returnItem;
}
}
可以看到我们在next中指定的遍历规则是根据ArrayMap的key值进行遍历。有了上述迭代器类,我们就可以使用iterator方式在外部对其进行遍历了,遍历代码如下:
ArrayMap<String, Integer> am = new ArrayMap<>();
am.put("hello", 5);
am.put("syrups", 10);
ArrayMap.KeyIterator ami = am.new KeyIterator();
while (ami.hasNext()) {
System.out.println(ami.next());
}
如上所示,通过创建KeyIterator对象进行迭代访问(注意外部类创建内部类对象的方式)。
支持for each循环
现在还不能支持for each循环访问,因为我们还没有实现iterable接口,首先在ArrayMap中实现Iterable接口:
public class ArrayMap<K, V> implements Iterable<K> {
private K[] keys;
private V[] values;
int size;
public ArrayMap() {
keys = (K[]) new Object[100];
values = (V[]) new Object[100];
size = 0;
}
....
}
然后重写iterator()方法,并在其中返回我们自己的迭代器对象(iterator)
@Override
public Iterator<K> iterator() {
return new KeyIterator();
}
注意我们自定义的KeyIterator类必须要实现Iterator接口,否则在iterator()方法中返回的类型不匹配。
总结与感想
(1)学会深入思考,一点点抽丝剥茧,多想想为什么这样实现,很多问题没有自己想象中的那么复杂。
(2)遇到疑惑不放弃,这是提升自己最好的机会,遇到某个疑难的点,解决的过程中会挖掘出很多相关东西。
参考资料:
(1)CS61B
(2)for each实现原理
Java中的Iterable与Iterator详解的更多相关文章
- Java 中的异常和处理详解
Java 中的异常和处理详解 原文出处: 代码钢琴家 简介 程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常.异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误 ...
- Java中23种经典设计模式详解
Java中23种设计模式目录1. 设计模式 31.1 创建型模式 41.1.1 工厂方法 41.1.2 抽象工厂 61.1.3 建造者模式 101.1.4 单态模式 131.1.5 原型模式 151. ...
- java中Map及Map.Entry详解
Map是java中的接口,Map.Entry是Map的一个内部接口. Map提供了一些常用方法,如keySet().entrySet()等方法. keySet()方法返回值是Map中key值的集合:e ...
- 关于Java中进程和线程的详解
一.进程:是程序的一次动态执行,它对应着从代码加载,执行至执行完毕的一个完整的过程,是一个动态的实体,它有自己的生命 周期.它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而 ...
- Java中的IO流系统详解(转载)
摘要: Java 流在处理上分为字符流和字节流.字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符.字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组. Java ...
- Java中String 的equals 和==详解
一.Java中数据存储区域包括: 1.寄存器:最快的存储区,由编译器根据需求进行分配,我们在程序中无法控制. 2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new ...
- java中vector与hashtable操作详解
众所周知,java中vector与hashtable是线程安全的,主要是java对两者的操作都加上了synchronized,也就是上锁了.因此 在vector与hashtable的操作是不会出现问题 ...
- Java中的IO流系统详解
Java 流在处理上分为字符流和字节流.字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符.字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组. Java 内用 U ...
- java中内存结构及堆栈详解
一. java内存结构 1. Heap(堆):实例分配的地方,通过-Xms与-Xmx来设置 2. MethodArea(方法区域):类的信息及静态变量. 对应是Permanet Generation, ...
随机推荐
- Android版数据结构与算法(三):基于链表的实现LinkedList源码彻底分析
版权声明:本文出自汪磊的博客,未经作者允许禁止转载. LinkedList 是一个双向链表.它可以被当作堆栈.队列或双端队列进行操作.LinkedList相对于ArrayList来说,添加,删除元素效 ...
- Lumen框架—升级改造之路-仓储层
仓储层与逻辑层搭建 Lumen官方文档:https://lumen.laravel.com/docs/5.5 我的项目地址:https://github.com/BusinessL/big-lume ...
- JavaScript 原型的深入指南
摘要: 理解prototype. 原文:JavaScript 原型的深入指南 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 不学会怎么处理对象,你在 JavaScript 道路就就走 ...
- 如何给自己的app添加分享到有道云笔记这样的功能
文章同步自http://javaexception.com/archives/34 如何给自己的app添加分享到有道云笔记这样的功能 问题: 在之前的一个开源笔记类项目Leanote中,有个用户反馈想 ...
- ORACLE字符集修改ORA-02374\ORA-12899\ORA-02372
IMPDP时部分日志显示这个警告ORA-02374: conversion error loading table "MEMXXX"."T_MEMBER_XXXX&quo ...
- CenOS_用户级别
1.运行级别 共有7个运行级别: 0:关机 1:单用户(不用密码也能登陆) 2:多用户无网络 3:多用户有网络 4:保留 5:图形化界面 6:重启 1.1设置运行级别语法: init <级别数字 ...
- The Network Adapter could not establish the connection
启动即可
- Java笔试题库之选题题篇【141-210题】
141.Struts框架可以支持以下哪种程序开发语言? A.C B.C++ C.Java D.C# 解答:C 142.在Servlet处理请求的方式为. A.以进程的方式 B.以程序的方式 C.以线程 ...
- C++ 之 简单的五子棋AI程序
本人是大一新生,寒假无聊,抱着试试看的心态(没有想到可以完成),写了C++的简单五子棋程序,开心. 下面是效果图: 一.首先讲讲大致思路. 五子棋实现的基础: ...
- 如何在ASP.NET Core程序启动时运行异步任务(1)
原文:Running async tasks on app startup in ASP.NET Core (Part 1) 作者:Andrew Lock 译者:Lamond Lu 背景 当我们做项目 ...