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 ...
随机推荐
- jquery not() 方法
1.not(expression) 根据表达式参数的值,从包装集里删除元素 example : $('img[alt]').not('[alt*=joy]') 返回包含属性alt的img元素,但img ...
- 1004. 填充矩形 (Standard IO)
题目描述 已知矩形的大小为n×m,现用a×a的正方形填充该矩形.输入三个正整数n,m,a(n,m,a≤10^9),计算至多能填入多少正方形?(正方形可以正好碰到矩形边界,但不能超出矩形外) 输入 一行 ...
- 如何解决git fatal: refusing to merge unrelated histories
我在Github新建一个仓库,写了License,然后把本地一个写了很久仓库上传. 先pull,因为两个仓库不同,发现refusing to merge unrelated histories,无法p ...
- oozie安装总结
偶然的机会,去面试的时候听面试官讲他们的调度系统是基于hue+oozie,以前一直没有接触过,今天趁有空,尝试一下oozie 1.环境说明 cat /etc/issue CentOS release ...
- Android 保存和恢复activity的状态数据
一般来说, 调用onPause()和onStop()方法后的activity实例仍然存在于内存中, activity的所有信息和状态数据不会消失, 当activity重新回到前台之后, 所有的改变都会 ...
- 安卓基础干货(六):安卓Activity的学习
AndroidManifest文件中的几个细节 结论: 1.一个应用程序可以创建多个桌面图标 2.创建快捷图标的方法: <intent-filter> //应用程序的入口 <acti ...
- Android--Otto事件总线 -- 组件之间通讯框架使用 --模式解析
前言:Otto事件总线 -- 组件之间通讯框架 对于之前的情况activity之间或者fragment之间等跳转传值一般都是用bundle.intent等,从activityA --- activit ...
- 【Supervised Learning】 集成学习Ensemble Learning & Boosting 算法(python实现)
零. Introduction 1.learn over a subset of data choose the subset uniformally randomly (均匀随机地选择子集) app ...
- ArcGIS 10.1 如何连接数据库(转)
原文地址:http://blog.csdn.net/arcgis_cs/article/details/7750893 ArcGIS 10.1如何连接数据库 最近在使用ArcGIS 10.1的数据库, ...
- Linux 内核超时导致虚拟机无法正常启动
问题描述 当 Linux 虚拟机启动时,通过串口输出或者启动日志, 观察到超时的报错.导致虚拟机无法正常启动和连接. 问题分析 常见的超时报错范例如下: 复制 INFO: task swapper:1 ...