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 ...
随机推荐
- Linux基础之命令练习Day1-init,who,date,cal,man,clear,passwd,su,whoami,mkdir,touch,rm,cp,mv,head,tail,more,less,echo
开启Linux操作系统,要求以root用户登录GNOME图形界面,语言支持选择为汉语 使用快捷键切换到虚拟终端2,使用普通用户身份登录,查看系统提示符 使用命令退出虚拟终端2上登录的用户 使用快捷键切 ...
- (转) AJAX POST&跨域 解决方案 - CORS
跨域是我在日常面试中经常会问到的问题,这词在前端界出现的频率不低,主要原因还是由于安全限制(同源策略, 即JavaScript或Cookie只能访问同域下的内容),因为我们在日常的项目开发时会不可避免 ...
- ie6 浏览器的bug
1.IE6不支持连续类的交集选择器 1 #box.box.box1{ 2 width: 200px; 3 height: 200px; 4 ...
- HTTP协议(持续更新)
http请求由三部分组成,分别是:请求行.消息报头.请求正文 HTTP(超文本传输协议)是一个基于请求与响应模式的.无状态的.应用层的协议,常基于TCP的连接方式,HTTP1.1版本中给出一种持续连接 ...
- 使用 Azure CLI 2.0 从自定义磁盘创建 Linux VM
本文说明如何在 Azure 中上传自定义的虚拟硬盘 (VHD) 或复制现有 VHD,并从自定义磁盘创建 Linux 虚拟机 (VM). 可以根据要求安装并配置 Linux 分发版,并使用该 VHD 快 ...
- mongodb 3.4复制集详解
1关闭数据库,打开三个mongodb数据库数据库实例 rs.printReplicationInfo() 2:原理 主库能够进行读写操作,一个复制集群只能有一个活跃的主库 一般情况下复制可以分为好几种 ...
- VSTO 开发中 应用ActionPane、CustomTaskPane
以Excel插件为例: 1. ActionPane 创建 ThisWorkbook 项目 private void ThisWorkbook_Startup(object sender, System ...
- Win10安装msi程序报错2503和2502错误解决方案
刚升级了系统到win10,重新搭建开发环境,在安装scala的时候一直报2503.2502错误,如图 试了好几种办法都不好使,现在罗列依次用到的三种方法: 一.命令提示符(管理员)启动 "w ...
- soapui使用。简单测试+测试套+负载测试。
http://www.cnblogs.com/zerotest/tag/soapui/
- 全国大学生数据挖掘邀请赛中的NDCG
转:http://www.zhizhihu.com/html/y2011/2794.html 评价标准 性能良好的评分模型,应该能够给予那些引起msg或click的候选会员更高的评分(排序靠前),从而 ...