Knowledge Point 20180308 拔下forEach的外衣
剖析加强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循环的原理。进而,我们再得出两个结论:
- ArrayList之所以能使用foreach循环遍历,是因为ArrayList所有的List都是Collection的子接口,而Collection是Iterable的子接口,ArrayList的父类AbstractList正确地实现了Iterable接口的iterator方法。注意:如果自己写的ArrayList用foreach循环直接报空指针异常是因为自己写的ArrayList并没有实现Iterable接口
- 任何一个集合,无论是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是一个语法糖,对于简便开发具有帮助,但是并不能用于性能提升。
- For-each语法内部,对collection是用iterator来实现的,对数组用下标遍历来实现。所以在对数组以外进行遍历的时候,要求实现接口Iterable。
- 加强for是一个语法糖,它帮助程序员简便开发,但是对于性能却并没有帮助,这个需要注意。Java 5 及以上的编译器隐藏了基于iterator和下标遍历的内部实现。(注意,这里说的是“Java编译器”或Java语言对其实现做了隐藏,而不是某段Java代码对其实现做了隐藏,也就是说,我们在任何一段JDK的Java代码中都找不到这里被隐藏的实现。这里的实现,隐藏在了Java 编译器中,我们可能只能像本文中说的那样,查看一段For-each的Java代码编译成的字节码,从中揣测它到底是怎么实现的了)
- 加强for循环只是JDK1.5提供的用于便于遍历的操作,并不能用来更改数组或集合,特别是更改集合可能会抛出异常,在这方面要留心注意;对于数组的操作是通过副本,对于集合如果想要在遍历时改变集合,则要使用迭代器,关于迭代器是集合中要讲解的,这里不做赘述。
本文只简单探讨加强for的底层实现,而不对迭代器进行探讨,迭代器放在集合中讲解。
Knowledge Point 20180308 拔下forEach的外衣的更多相关文章
- win7插着网线开机卡死,拔下网线开机正常
公司的部分win7电脑插着网线开机,进到桌面后网络图标转圈圈卡住.控制面板,启动项,任务管理器等都打不开.把网线拔下后再开机,电脑正常进入系统,后再插上网线就能正常上网了.被这个问题困扰了很久,百度也 ...
- U盘启动装完系统后 一拔下优盘 就不能进入系统
PE下Ghost安装的,装好进入系统正常,可是拔下 u盘就进不去系统,而插上 u盘就好好的 原因:引导的事,找到本地硬盘第一分区并且激活! 就可以了! 可用下“电脑店-修复主引导记录(MBR)工具”h ...
- MyBatis_动态sql_foreach_mysql下foreach批量插入的两种方式
方法1: 笔记要点出错分析与总结工程组织数据库组织0.重新修改Bean类 修改1.定义接口 //批量插入 public void addEmps(@Param("emps") ...
- Knowledge Point 20180308 Dead Code
不知道有没有前辈注意过,当你编写一段“废话式的代码时”会给出一个Dead Code警告,点击警告,那么你所写的废物代码会被编译器消除,那么如果你不理睬这个警告呢?编译后会是什么样的呢?下面我们写点代码 ...
- win10拔下电源会黑一下屏
- Ubuntu 64位下搭建ADT的种种问题
我使用的adt版本为 adt-bundle-linux-x86_64-20140702.zip 1. Eclipse启动时提示 adb 无法加载动态链接库 libstdc++.so.6 以及 lib ...
- Notes 20180308 : 语句
在讲解流程控制语句之前,我们先来说一下语句的问题.Java中的语句分为声明和赋值语句,条件和循环语句,调用和返回语句:我们之所以每两个放在一起是有深意的,我们大致将语句分为这三块,并以此为纲来说一下, ...
- foreach
一 foreach的语法介绍 PHP 4以上的版本包括了 foreach 结构,这只是一种遍历数组简便方法.foreach 仅能用于数组,当试图将其用于其它数据类型或者一个未初始化的变量时会产生 ...
- [译]何时使用 Parallel.ForEach,何时使用 PLINQ
原作者: Pamela Vagata, Parallel Computing Platform Group, Microsoft Corporation 原文pdf:http://download.c ...
随机推荐
- vue-cli项目打包多个与static文件同级的静态资源目录(copy-webpack-plugin插件的使用)
场景 业务要求能够直接通过 "域名+/file"的方式访问静态资源的html,然而产品绝对static暴露在url中不好看又不能直接将html放在static中.所以想到了既然st ...
- 如何解决git fatal: refusing to merge unrelated histories
我在Github新建一个仓库,写了License,然后把本地一个写了很久仓库上传. 先pull,因为两个仓库不同,发现refusing to merge unrelated histories,无法p ...
- 如何创建一个基本JQuery的插件
如何创建一个基本的插件 有时您希望在整个代码中提供一些功能.例如,也许你想要一个单一的方法,你可以调用一个jQuery选择,对选择执行一系列的操作.在这种情况下,您可能需要编写一个插件. 链接jQue ...
- hdu 1162 Eddy's picture (Kruskal 算法)
题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=1162 Eddy's picture Time Limit: 2000/1000 MS (Java/Ot ...
- lianxi
package dududu; public class qiqiqi { public static void main(String[] args) { // TODO 自动生成的方法存根 ; ; ...
- Genymotion模拟器拖入文件报An error occured while deploying the file的错误
今天需要用到资源文件,需要将资源文件拖拽到sd卡中,但老是出现这个问题: 资源文件拖不进去genymotion.查看了sd的DownLoad目录,确实没有成功拖拽进去. 遇到这种问题的,我按下面的思路 ...
- strace for Android
使用strace for Android跟踪系统调用过程方便后续的so文件分析 http://benno.id.au/blog/2007/11/18/android-runtime-stracehtt ...
- IDEA maven 多项目 出现 java 程序包找不到
使用idea 多项目引入的时候,出现报错信息:Error java程序包找不到 解决办法: 我在引入多个项目的时候,他们是在一个目录里面的.我把整个目录引入了进去.结果报依赖包找不到. 把引入的全部项 ...
- 【PHP系列】框架的抉择
缘起 在PHP开发中,选择合适的框架有助于加快软件开发,节约宝贵的项目时间,让开发者专注于功能的实现上.框架的问题是需要很多的投入,选择框架时,我们更看重这个框架的未来,存在多年的大型框架必须要有好的 ...
- 并发包Semaphore实现信号灯
/** * * @描述: Semaphore实现信号灯 . * Semaphore可以维护当前访问自身的线程个数,并提供了同步机制,使用Semaphore可以控制同时访问资源的线程个数,例如实现一个文 ...