剖析加强for  

  很长一段时间对于foreach都有一种误解,那就是foreach只是普通for的包装,底层还是普通for循环,直到深入了解迭代器的时候,才发现自己错了,本节就来探讨一下foreach,深入底层去了解它。下面我们通过一段代码来看一下:

    public static void listIterator(){
List<String> list = new ArrayList<>();
list.add("野有蔓草,零露漙兮。");
list.add("有美一人,清扬婉兮。");
list.add("邂逅相遇,适我愿兮。");
list.add("野有蔓草,零露瀼瀼。");
list.add("有美一人,婉如清扬。");
list.add("邂逅相遇,与子偕臧。");
for (String string : list) {
System.out.println(string);
}
}

  想要了解它的底层实现,自然需要它编译后的代码,下面我们通过反编译查看一下:

  这样看起来可能不好理解,我找了一款反编译软件,查看反编译后的结果:

    public static void listIterator()
{
List list = new ArrayList();
list.add("\u91CE\u6709\u8513\u8349\uFF0C\u96F6\u9732\u6F19\u516E\u3002");
list.add("\u6709\u7F8E\u4E00\u4EBA\uFF0C\u6E05\u626C\u5A49\u516E\u3002");
list.add("\u9082\u9005\u76F8\u9047\uFF0C\u9002\u6211\u613F\u516E\u3002");
list.add("\u91CE\u6709\u8513\u8349\uFF0C\u96F6\u9732\u703C\u703C\u3002");
list.add("\u6709\u7F8E\u4E00\u4EBA\uFF0C\u5A49\u5982\u6E05\u626C\u3002");
list.add("\u9082\u9005\u76F8\u9047\uFF0C\u4E0E\u5B50\u5055\u81E7\u3002");
String string;
for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(string))
string = (String)iterator.next();
}

  上面的代码我们知道了两个问题,Java真的是通过Unicode编码的,不过这不是重点,重点是遍历,我们发现编译器将加强for循环,编程了普通for循环,通过迭代器进行循环操作。

  在编译的时候编译器会自动将对for这个关键字的使用转化为对目标的迭代器的使用,这就是foreach循环的原理。进而,我们再得出两个结论:

  1. ArrayList之所以能使用foreach循环遍历,是因为ArrayList所有的List都是Collection的子接口,而Collection是Iterable的子接口,ArrayList的父类AbstractList正确地实现了Iterable接口的iterator方法。注意:如果自己写的ArrayList用foreach循环直接报空指针异常是因为自己写的ArrayList并没有实现Iterable接口
  2. 任何一个集合,无论是JDK提供的还是自己写的,只要想使用foreach循环遍历,就必须正确地实现Iterable接口;

  实际上,这种做法就是23中设计模式中的迭代器模式。查看了加强for对集合的操作,我们思考一个问题,如果加强for是对实现Iterable接口的一种优化,那么数组呢?Arrays并没有实现该接口,但是加强for仍然能够应用于数组,下面我们再来看一下数组的加强for遍历:

public static void arrFor(){
int[] arr = {1,2,3,4,5};
for (int i : arr) {
System.out.println(i);
}
}

一样通过反编译查看:

  public static void arrFor()
{
int arr[] = {
1, 2, 3, 4, 5
};
int ai[];
int k = (ai = arr).length;
for(int j = 0; j < k; j++)
{
int i = ai[j];
System.out.println(i);
} }

观察发现Java将对于数组的foreach循环转换为对于这个数组每一个的循环引用,且操作的是数组的一个备份,而非是数组本身,所以在加强for中对于数组的操作,不会影响到数组本身,通过下面的例子观察一下:

public static void arrFor(){
int[] arr = {1,2,3,4,5};
for (int i : arr) {
i = 9;
System.out.println(i);
}
System.out.println(Arrays.toString(arr));
}

  但是对于集合却和数组不同了,集合可以直接操作到本身,我们还是通过例子查看:

    public static void listIterator(){
List<String> list = new ArrayList<>();
list.add("野有蔓草,零露漙兮。");
list.add("有美一人,清扬婉兮。");
list.add("邂逅相遇,适我愿兮。");
list.add("野有蔓草,零露瀼瀼。");
list.add("有美一人,婉如清扬。");
list.add("邂逅相遇,与子偕臧。");
for (String string : list) {
// string = "改变";
if(string.equals("邂逅相遇,适我愿兮。")){
list.remove(string);
}
System.out.println(string);
}
System.out.println(list.toString());
}

我们发现在加强for中对于集合的改变是不被允许的,将注释去掉,同时注释掉删除语句,查看结果如下:

我们发现仍然对于集合没有改变,使用JDK1.5提供的foreach循环来迭代访问集合元素更加便捷。当使用foreach循环迭代访问集合元素时,该集合也不能被改变。

  第一种remove时的异常是因为,更改了集合长度,此时后台方法(ArrayList)中判断抛出异常

  而在加强for中改变元素的内容,断点调试发现,改变只是将字符串的索引改变了,但是集合中字符串指向并没有改变;

总结:

  通过对于foreach的剖析,我们知道,加强for会在编译器编译后回归到普通for循环,加强for语法内部对于其操作有两种实现(数组和集合),加强for是一个语法糖,对于简便开发具有帮助,但是并不能用于性能提升。

  1. For-each语法内部,对collection是用iterator来实现的,对数组用下标遍历来实现。所以在对数组以外进行遍历的时候,要求实现接口Iterable。
  2. 加强for是一个语法糖,它帮助程序员简便开发,但是对于性能却并没有帮助,这个需要注意。Java 5 及以上的编译器隐藏了基于iterator和下标遍历的内部实现。(注意,这里说的是“Java编译器”或Java语言对其实现做了隐藏,而不是某段Java代码对其实现做了隐藏,也就是说,我们在任何一段JDK的Java代码中都找不到这里被隐藏的实现。这里的实现,隐藏在了Java 编译器中,我们可能只能像本文中说的那样,查看一段For-each的Java代码编译成的字节码,从中揣测它到底是怎么实现的了)
  3. 加强for循环只是JDK1.5提供的用于便于遍历的操作,并不能用来更改数组或集合,特别是更改集合可能会抛出异常,在这方面要留心注意;对于数组的操作是通过副本,对于集合如果想要在遍历时改变集合,则要使用迭代器,关于迭代器是集合中要讲解的,这里不做赘述。

本文只简单探讨加强for的底层实现,而不对迭代器进行探讨,迭代器放在集合中讲解。

Knowledge Point 20180308 拔下forEach的外衣的更多相关文章

  1. win7插着网线开机卡死,拔下网线开机正常

    公司的部分win7电脑插着网线开机,进到桌面后网络图标转圈圈卡住.控制面板,启动项,任务管理器等都打不开.把网线拔下后再开机,电脑正常进入系统,后再插上网线就能正常上网了.被这个问题困扰了很久,百度也 ...

  2. U盘启动装完系统后 一拔下优盘 就不能进入系统

    PE下Ghost安装的,装好进入系统正常,可是拔下 u盘就进不去系统,而插上 u盘就好好的 原因:引导的事,找到本地硬盘第一分区并且激活! 就可以了! 可用下“电脑店-修复主引导记录(MBR)工具”h ...

  3. MyBatis_动态sql_foreach_mysql下foreach批量插入的两种方式

    方法1: 笔记要点出错分析与总结工程组织数据库组织0.重新修改Bean类    修改1.定义接口 //批量插入 public void addEmps(@Param("emps") ...

  4. Knowledge Point 20180308 Dead Code

    不知道有没有前辈注意过,当你编写一段“废话式的代码时”会给出一个Dead Code警告,点击警告,那么你所写的废物代码会被编译器消除,那么如果你不理睬这个警告呢?编译后会是什么样的呢?下面我们写点代码 ...

  5. win10拔下电源会黑一下屏

  6. Ubuntu 64位下搭建ADT的种种问题

    我使用的adt版本为 adt-bundle-linux-x86_64-20140702.zip 1. Eclipse启动时提示 adb 无法加载动态链接库 libstdc++.so.6 以及  lib ...

  7. Notes 20180308 : 语句

    在讲解流程控制语句之前,我们先来说一下语句的问题.Java中的语句分为声明和赋值语句,条件和循环语句,调用和返回语句:我们之所以每两个放在一起是有深意的,我们大致将语句分为这三块,并以此为纲来说一下, ...

  8. foreach

    一 foreach的语法介绍    PHP 4以上的版本包括了 foreach 结构,这只是一种遍历数组简便方法.foreach 仅能用于数组,当试图将其用于其它数据类型或者一个未初始化的变量时会产生 ...

  9. [译]何时使用 Parallel.ForEach,何时使用 PLINQ

    原作者: Pamela Vagata, Parallel Computing Platform Group, Microsoft Corporation 原文pdf:http://download.c ...

随机推荐

  1. jQuery——子元素筛选器

    子元素筛选器 名称 :first-child JQ语法 jQuery( "selector:first-child" ) 说明 :first-child选择器用于匹配作为父元素的第 ...

  2. javascript时间格式转换(今天,昨天,前天)

    function transDate() { var $time =document.getElementById("share-time"); var date = $time. ...

  3. 移动端meta标签的使用和设置

    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale= ...

  4. mysql创建用户授权

    服务器上好多库,为了不让某用户误操作别人的数据库,给他单独建个用户并授权. insert into mysql.user(Host,User,Password, ssl_cipher, x509_is ...

  5. 理解LSTM/RNN中的Attention机制

    转自:http://www.jeyzhang.com/understand-attention-in-rnn.html,感谢分享! 导读 目前采用编码器-解码器 (Encode-Decode) 结构的 ...

  6. java音频播放器

    java音频播放器备份,支持wav,mp3 都是摘抄于网络,wav播放,mp3播放 播放wav版本 包: 不需要其他jar包 代码: package com; import javax.sound.s ...

  7. Dubbo框架介绍与安装 Dubbo 注册中心(Zookeeper-3.4.6)

    背景 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. • 单一应用架构 • 当网站流量很小时, ...

  8. LINQ学习笔记(一)基本语法

    1.LINQ简介 LINQ是Language Integrated Query的简称,它是集成在.NET编程语言中的一种特性.包括五个部分:LINQ to Objects.LINQ to DataSe ...

  9. 【转】Java中的多线程你只要看这一篇就够了

    https://www.jianshu.com/p/40d4c7aebd66 引 如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的, ...

  10. Linux:配置HTTPS,获取ssl证书

    这里小仓鼠将Https配置流程记录下来 1.访问网站:https://www.aliyun.com/ss/?k=ssl 2.选择‘立即购买’ 3. 页面变换为: 4.点击‘立即购买’ 5.进行支付 6 ...