迭代器Iterator与语法糖for-each

一、为什么需要迭代器

设计模式迭代器

迭代器作用于集合,是用来遍历集合元素的对象。迭代器不是Java独有的,大部分高级语言都提供了迭代器来遍历集合。实际上,迭代器是一种设计模式:迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

迭代器封装了对集合的遍历,使得不用了解集合的内部细节,就可以使用同样的方式遍历不同的集合。

暴露细节的遍历

要理解封装遍历的好处,必须理解暴露细节的遍历带来的坏处。

以下是两个不同的集合接口,第一个是自定义集合,第二个是JDK的java.util.Listpublic interface IUserDefinedList {    int length();    E getElement(int index);

}public interface List {    int size();    E get(int index);

}

分别使用for循环对它们进行遍历:// 自定义Listfor (int i = 0, len = ul.length(); i

System.out.println(ul.getElement(i));

}// java.util.Listfor (int i = 0, size = ll.size(); i

System.out.println(ll.get(i));

}

遍历集合的代码与具体集合类型紧密耦合,不同类型的集合,必须写出不同的遍历代码,不可重用。紧耦合在良好的代码设计中是大忌,这时需要将遍历逻辑抽离,封装。这就是迭代器模式了。

二、封装遍历-迭代器面向接口编程

面向接口编程是基本的设计原则,迭代器模式将遍历封装到接口,然后各个集合类可以以实现接口,或者组合接口实现类的方式,将遍历封装。

迭代器接口如下:public interface Iterator {    boolean hasNext();    E next();

}

然后就可以以统一的方式遍历集合:Iterator it = aIterator;while(it.hasNext) {

it.next();

}

因为集合都实现了Iterator接口,所以以上的遍历代码是可以重用的,并且与具体集合类型松耦合。

Java迭代器

Java提供的Iterator原理大致相同:

java迭代器接口

忽略Java8提供的默认方法forEachRemaining(),java迭代器多了可以移除上一个元素的remove()方法。

Java集合中有很多迭代器的具体实现,以ArrayList为例:

ArrayList迭代器实现

ArrayList的迭代器是以内部类的方式实现的,每次调用List的iterator()方法,都会得到一个基于当前ArrayList对象状态的迭代器:public Iterator iterator() {    return new Itr();

}

next()方法:public E next() {

checkForComodification();    int i = cursor;    if (i >= size)        throw new NoSuchElementException();

Object[] elementData = ArrayList.this.elementData;    if (i >= elementData.length)        throw new ConcurrentModificationException();

cursor = i + 1;    return (E) elementData[lastRet = i];

}

每次调用next()方法,都会调用final void checkForComodification() {   if (modCount != expectedModCount)        throw new ConcurrentModificationException();

}

检查所迭代的列表对象是否被修改过,modCount是外部类的一个字段,当调用外部类的add, remove, ensureCapacity等方法,都会改变改字段的值,而expectedModCount是内部类Itr初始化迭代器实例时,使用modeCount赋值的字段。这样,在使用迭代器过程中,如果对正在迭代的对象调用了add, remove, ensureCapacity等方法,再去调用迭代器的next(),就会引发ConcurrentModificationException异常了。

一个诱发异常的例子:ArrayList ls = new ArrayList<>();

Iterator it = ls.iterator();while (it.hasNext()) {

System.out.println(it.next());

ls.ensureCapacity(10); // 改变了集合对象modCount的值,下次调用next 抛出异常}

三、语法糖for-each

java中的for-each:List ls = new ArrayList<>();for (String s: ls) {

System.out.println(s);

}

for-each其实只是java提供的语法糖。语法糖是编程语言提供的一些便于程序员书写代码的语法,是编译器提供给程序员的糖衣,编译时会对这些语法特殊处理。语法糖虽然不会带来实质性的改进,但是在提高代码可读性,提高语法严谨性,减少编码错误机会上确实做出了很大贡献。

Java要求集合必须实现Iterable接口,才能使用for-each语法糖遍历该集合的实例。

JDK对该接口的描述是:Implementing this interface allows an object to be the target of * the "for-each loop" statement.

接口如下:public interface Iterable {

Iterator iterator();

default void forEach(Consumer super T> action) {

Objects.requireNonNull(action);        for (T t : this) {

action.accept(t);

}

}

default Spliterator spliterator() {        return Spliterators.spliteratorUnknownSize(iterator(), 0);

}

}

忽略默认方法,该接口要求集合实现iterator()方法,并返回一个迭代器对象Iterator。这也是java中的集合通过实现Iterable接口,组合Iterator来提供迭代器,并不通过直接实现Iterator接口的方式来提供集合迭代器的原因了。所有java集合迭代器的直接用法都如下:List ls = new ArrayList<>();

Iterator it = ls.iterator();while (it.hasNext()) {

System.out.println(it.next());

}

for-each的编译器实现

for-each遍历集合,实际上被翻译如下:for (I #i = Expression.iterator(); #i.hasNext(); ) {

{VariableModifier} TargetType Identifier =

(TargetType) #i.next();

Statement

}

当然,除了集合,for-each还可以遍历数组,翻译如下:T[] #a = Expression;

L1: L2: ... Lm:for (int #i = 0; #i

{VariableModifier} TargetType Identifier = #a[#i];

Statement

}

另外,使用javap命令,反编译字节码,可以看到编译器是怎样处理for-each的。public class Test {    public static void main(String[] args) {

List ls = new ArrayList<>();        for (String s: ls) {

}

}

}

以上源文件对应class文件的反编译汇编为:C:\OTHERS\Working\ideaWork\utils\Z_practice\src\main\java>javap -c pattern\adapter\java_example\Test.class

Compiled from "Test.java"public class pattern.adapter.java_example.Test {  public pattern.adapter.java_example.Test();

Code:       0: aload_0       1: invokespecial #1                  // Method java/lang/Object."":()V

4: return

public static void main(java.lang.String[]);

Code:       0: new           #2                  // class java/util/ArrayList

3: dup       4: invokespecial #3                  // Method java/util/ArrayList."":()V

7: astore_1       8: aload_1       9: invokeinterface #4,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;

14: astore_2      15: aload_2      16: invokeinterface #5,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z

21: ifeq          37

24: aload_2      25: invokeinterface #6,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;

30: checkcast     #7                  // class java/lang/String

33: astore_3      34: goto          15

37: return}

不用关注全部汇编细节,只需要看到InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;就知道实际上是使用了迭代器。

至于数组的for-each,也可以通过观察class文件的汇编代码理解。

四、最佳实践for-each遍历的集合对象不能为null

既然对集合的for-each遍历实际上是使用迭代器,会调用集合对象的iterator()方法获得迭代器,那么,对null集合的for-each遍历,就会在null集合对象上调用方法,势必会抛出空指针异常。

for-each遍历时不能改变正在遍历的集合

因为在使用迭代器遍历集合时,不能够改变集合,所以for-each遍历时改变集合,同样会引发ConcurrentModificationException异常。

作者:理查德成

链接:https://www.jianshu.com/p/186bf11ffe51

java for 迭代器_Java基础-迭代器Iterator与语法糖for-each相关推荐

  1. java集合迭代器_java集合迭代器

    一.Java中有一个设计模式是迭代器模式 1.迭代器模式定义 迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示. 2.迭代器模式概述 Java集 ...

  2. java 刽子手游戏_java基础(九):容器

    集合的引入 List (ArrayList LinkedList) Set (HashSet LinkedHashSet TreeSet ) Map (HashMap LinkedHashMap Tr ...

  3. java操作符重载_Java基础知识-操作符重载

    java操作符重载 String str1 = "hello"; String str2 = str1 + " world"; System.out.print ...

  4. ef 在此上下文中只支持基本类型或枚举类型_Java 中的 6 颗语法糖

    作者:Java 技术栈来源:SegmentFault 思否社区 原文作者:danchu原文链接:https://blog.csdn.net/danchu/article/details/5498644 ...

  5. java try resource_从 Java 字节码角度看 try with resource 语法糖

    Java 7中的 try-with-resource,在没有这个语法糖的情况下的等价实现是什么? 以下面的demo为例,这个问题目测99%的人都写不完全正确,不信来战. public static v ...

  6. java list 差集_Java基础之集合框架

    Java 集合框架概述 一方面, 面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储.另一方面,使用Array存储对象方面具有一些弊端,而Java 集合就像一种容器 ...

  7. java input 数组_Java基础之:数组

    Java基础之:数组 一组相同数据类型的数据,我们即称之为 数组,数组也是一种数据类型. 需要注意的是 , 数组和String 字符串 相同,也是引用类型的. 数组的初始化 方式一:动态初始化 四种格 ...

  8. java代码讲解_Java基础系列-代码块详解

    注:本文出自博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 前言 Java基础系列,尽量采用通俗易懂.循序渐进的方式,让大家真正理解Java基础知识! 代码块 ...

  9. java单线程循环调度_Java基础篇之Java线程模型

    原标题:Java基础篇之Java线程模型 Java运行系统在很多方面依赖于线程,所有的类库设计都考虑到多线程.实际上,Java使用线程来使整个环境异步.这有利于通过防止CPU循环的浪费来减少无效部分. ...

最新文章

  1. 揭秘 Uber API 网关的架构,建议收藏!
  2. scrapy安装出错
  3. 在CentOS7上部署.net core 控制台应用程序部署为后台服务
  4. Atom 相关配置备份
  5. boost::hana::to用法的测试程序
  6. 最优化作业02—一维最优化方法
  7. fiddler工具条、状态栏、请求信息栏各按钮的作用
  8. 数据结构-栈5-栈的应用-后缀转中缀
  9. Dictionary 序列化与反序列化
  10. WORD里的背景水印为什么怎么也删除不了呢?
  11. 推荐个 Java 开源商城项目,这个是真的好!
  12. DDOS误判怎么预防
  13. 访谈录#1:成为“温赵轮”
  14. Mac IntelliJIDEA非正常关闭解决(reopen失败)
  15. Java—sql关于不同条件下合并结果
  16. 电商小程序实战教程-分类导航
  17. PreScan 教程:0. PreScan与Matlab连接
  18. vs的快捷键——注释/取消注释
  19. [ Laravel 5.5 文档 ] 快速入门 —— 使用 Laragon 在 Windows 中搭建 Laravel 开发环境
  20. WGCLOUD——如何统计用户的日活(dau)、月活(mau)数据

热门文章

  1. keras环境搭建 [过程记录]
  2. SAP Commerce Cloud Accelerator Checkout Delivery Mode 选择页面的实现 JSP
  3. 介绍一个能够对日志文件进行自定义高亮的 VS Code 扩展
  4. SAP Spartacus pop over 元素的单元测试
  5. SAP Spartacus 用户认证的实现
  6. SAP Spartacus CMS Component的lazy loading懒加载方式
  7. SAP Spartacus home 页面的 cx-page-slot selector
  8. SAP Spartacus如何创建自定义route页面
  9. 在SAP CAL(Cloud Application Library)上搭建ABAP HANA系统
  10. SAP UI5 Repository and MongoDB Repository