foreach底层机制
简单例子
直接了解foreach底层有些困难,我们需要从更简单的例子着手.下面上一个简单例子:
public class Simple { public static void main(String[] args) {
int i = ;
System.out.println(i);
}
}
找到其字节码文件所在目录并在目录下打开终端(Windows系统是在目录下shift+鼠标右键选择在此打开powershell窗口)
输入指令:javac -Simple.class >SimpleRunc
目录中得到SimpleRunc文件,打开它,会看到如下代码(里面有我的注释):
Compiled from "Simple.java"
public class cn._012thDay._foreach.Simple {
public cn._012thDay._foreach.Simple();
Code:
: aload_0 将第一个引用型本地变量推送至栈顶;
: invokespecial # // Method java/lang/Object."<init>":()V
调用超类构造方法;
: return public static void main(java.lang.String[]);
Code:
: iconst_5 将int型5推送至栈顶;
: istore_1 将栈顶int型数据存入第二个本地变量;
此处推测:第一个本地变量是超类; : getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
获取本地静态域并将其压入栈顶;即获取静态属性压入栈顶
: iload_1 将第二个int型本地变量推送至栈顶;
: invokevirtual # // Method java/io/PrintStream.println:(I)V
调用实例方法;
获取类属性,加载入栈,打印栈顶,即5 : return
}
如果不懂指令意思,可查询JVM指令表.这里我说明一下步骤:
第一步:加载超类Object类
第二步:将int类型的5压入栈顶,然后将5存入本地变量1
第三部:获取静态属性
第四步:加载本地变量1,即将5推送至栈顶
第五步:打印
for循环
public class ForSimple { public static void main(String[] args) {
for (int i = ; i > ; i-=) {
System.out.println(i);
}
}
}
其实这个例子foreach就做不了,因为foreach遍历必须要有一个数组.
javap -c:
Compiled from "ForSimple.java"
public class cn._012thDay._foreach.ForSimple {
public cn._012thDay._foreach.ForSimple();
Code:
: aload_0
: invokespecial # // Method java/lang/Object."<init>":()V
: return public static void main(java.lang.String[]);
Code:
: iconst_5
: istore_1
: goto
: getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
: iload_1
: invokevirtual # // Method java/io/PrintStream.println:(I)V
: iinc , -
: iload_1
: ifgt
: return
}
main方法第2行goto 15代表无条件跳转至第15行加载变量1,值是5
16行:ifgt 5 如果int型大于零则回到第5行,5>0
5~9行:打印,5
12行:变量1自增-2,即5变为3
15行:加载变量1,值是3
16行:ifgt 5 如果int型大于零则回到第5行,3>0
5~9行:打印,3
12行:变量1自增-2,即3变为1
15行:加载变量1,值是1
16行:ifgt 5 如果int型大于零则回到第5行,1>0
5~9行:打印,1
12行:变量1自增-2,即1变为-1
15行:加载变量1,值是-1
16行:ifgt 5 如果int型大于零则回到第5行,-1 <0
19行:return main方法结束
for循环打印结果为5,3,1
foreach遍历
先new一个int[]数组,看看数据是如何存储的:
public class SimpleDemo { public static void main(String[] args) {
// TODO Auto-generated method stub
int[]arr = new int[];
arr[] = ;
arr[] = ;
arr[] = ;
}
}
Compiled from "SimpleDemo.java"
public class cn._012thDay._foreach.SimpleDemo {
public cn._012thDay._foreach.SimpleDemo();
Code:
: aload_0
: invokespecial # // Method java/lang/Object."<init>":()V
: return public static void main(java.lang.String[]);
Code:
: iconst_3
: newarray int
: astore_1
: aload_1
: iconst_0
: bipush
: iastore
: aload_1
: iconst_1
: bipush
: iastore
: aload_1
: iconst_2
: bipush
: iastore
: return
}
前3行创建基本类型(int)数组,长度为3,存入本地引用型变量1
将索引为0的位置压入10并存储
将索引为1的位置压入20并存储
将索引为2的位置压入30并存储
接下来开始遍历,加入for循环:
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
Compiled from "SimpleDemo.java"
public class cn._012thDay._foreach.SimpleDemo {
public cn._012thDay._foreach.SimpleDemo();
Code:
: aload_0
: invokespecial # // Method java/lang/Object."<init>":()V
: return public static void main(java.lang.String[]);
Code:
: iconst_3
: newarray int
: astore_1
: aload_1
: iconst_0
: bipush
: iastore
: aload_1
: iconst_1
: bipush
: iastore
: aload_1
: iconst_2
: bipush
: iastore
: iconst_0
: istore_2
: goto
: getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
: aload_1
: iload_2
: iaload
: invokevirtual # // Method java/io/PrintStream.println:(I)V
: iinc ,
: iload_2
: aload_1
: arraylength
: if_icmplt
: return
}
上面代码大家应该不那么陌生了,前面18行存入数组,第19行开始创建了一个新的变量int型值为0,存入变量2.然后用变量2和数组长度作比较,小于数组长度就回到第24行打印,这是一个典型的for循环.
整个遍历中不考虑超类的加载总共创建了两个本地变量,即arr[3]和int i,用arr[3]的长度3和i进行比较,符合条件输出arr[i].输出结果为10,20,30
下面终于轮到我们的主角foreach登场了,删除for循环,新增foreach迭代:
for (int item : arr) {
System.out.println(item);
}
Compiled from "SimpleDemo.java"
public class cn._012thDay._foreach.SimpleDemo {
public cn._012thDay._foreach.SimpleDemo();
Code:
: aload_0
: invokespecial # // Method java/lang/Object."<init>":()V
: return public static void main(java.lang.String[]);
Code:
: iconst_3
: newarray int
: astore_1
: aload_1
: iconst_0
: bipush
: iastore
: aload_1
: iconst_1
: bipush
: iastore
: aload_1
: iconst_2
: bipush
: iastore
: aload_1 load local1 :
: dup copy
: astore int[] local5 = local1
: arraylength
: istore int local4 =
: iconst_0
: istore_3 int local3 =
: goto
: aload load local5 : int[]
: iload_3 load local3 : ..
: iaload arr[..]进栈
: istore_2 int local2 = arr[..]
: getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
: iload_2 load local2 :arr[..]
: invokevirtual # // Method java/io/PrintStream.println:(I)V
: iinc , local3 +=
: iload_3 load local3 :..
: iload load local4 :
: if_icmplt local3 < local4 ? go line31:next line
: return
}
以上代码我加入了注释,这里大家应该可以看懂了,不考虑超类加载,foreach总共创建了5个本地变量:
local1是原始数组,引用类型
local5是原始数组副本,引用类型
local4是副本数组长度,int类型
local3是0,int类型
local2是arr[i]的副本,int类型
总结:
1.for循环和foreach循环底层创建变量数不同,对于遍历int[]类型数组,for循环底层创建2个本地变量,而foreach底层创建5个本地变量;
2.for循环直接对数组进行操作,foreach对数组副本进行操作;
由于foreach是对数组副本操作,开发中可能导致的问题:
附上java代码和javap -c代码
public class ForeachDemo { public static void main(String[] args) {
// TODO Auto-generated method stub String[] s1 = new String[];
for (String item : s1) {//这里的s1实际是s1副本
item = new String("b");
System.out.println(item);//这里可以把副本中的每项打印出来
}
print(s1);//打印s1是null,因为s1在内存地址中没有任何变化 } private static void print(String[] s) {
// TODO Auto-generated method stub
for (int i = ; i < s.length; i++) {
System.out.print(s[i]+" ");
}
} }
Compiled from "ForeachDemo.java"
public class cn._012thDay._foreach.ForeachDemo {
public cn._012thDay._foreach.ForeachDemo();
Code:
: aload_0
: invokespecial # // Method java/lang/Object."<init>":()V
: return public static void main(java.lang.String[]);
Code:
: iconst_3
: anewarray # // class java/lang/String
: astore_1
: aload_1
: dup
: astore
: arraylength
: istore
: iconst_0
: istore_3
: goto
: aload
: iload_3
: aaload
: astore_2
: new # // class java/lang/String
: dup
: ldc # // String b
: invokespecial # // Method java/lang/String."<init>":(Ljava/lang/String;)V
: astore_2
: getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
: aload_2
: invokevirtual # // Method java/io/PrintStream.println:(Ljava/lang/String;)V
: iinc ,
: iload_3
: iload
: if_icmplt
: aload_1
: invokestatic # // Method print:([Ljava/lang/String;)V
: return
}
javap -c代码第7行新建了String[]数组副本变量5,之后一直在对副本进行操作,直到48行aload_1,然后打印,此时不难看出foreach中进行的所有操作都没有对本地变量1(即原数组)的值产生任何影响.
所以main方法最后一行打印数组s1,其结果一定是3个null
foreach底层机制的更多相关文章
- [转]STL 容器一些底层机制
1.vector 容器 vector 的数据安排以及操作方式,与 array 非常相似.两者的唯一区别在于空间的运用的灵活性.array 是静态空间,一旦配置了就不能改变,vector 是动态数组.在 ...
- C++ STL容器底层机制
1.vector容器 vector的数据安排以及操作方式,与array非常相似.两者的唯一区别在于空间的运用的灵活性.array是静态空间,一旦配置了就不能改变.vector是动态空间,随着元素的加入 ...
- 探索C++的底层机制
探索C++的底层机制 在看这篇文章之前,请你先要明白一点:那就是c++为我们所提供的各种存取控制仅仅是在编译阶段给我们的限制,也就是说是编译器确保了你在完成任务之前的正确行为,如果你的行为不正确,那么 ...
- tensorflow入门教程和底层机制简单解说——本质就是图计算,自动寻找依赖,想想spark机制就明白了
简介 本章的目的是让你了解和运行 TensorFlow! 在开始之前, 让我们先看一段使用 Python API 撰写的 TensorFlow 示例代码, 让你对将要学习的内容有初步的印象. 这段很短 ...
- 20191031:Python底层机制
20191031:Python底层机制 python底层从3个方面来说,分别是: 引用计数机制 垃圾回收机制 内存池机制 引用计数机制 使用引用计数来追踪内存中的对象,所有对象都有引用计数,并且这个引 ...
- php-浅谈php底层机制
php-浅谈php底层机制 1. PHP的设计理念及特点 多进程模型:由于PHP是多进程模型,不同请求间互不干涉,这样保证了一个请求挂掉不会对全盘服务造成影响,当然,随着时代发展,PHP也早已支持多线 ...
- ②NuPlayer播放框架之ALooper-AHandler-AMessage底层机制分析
[时间:2016-09] [状态:Open] [关键词:android,NuPlayer,开源播放器,播放框架,ALooper,AHandler,AMessage] 前文中提到过NuPlayer基于S ...
- Java 底层机制(JVM/堆/栈/方法区/GC/类加载)
转载:https://www.jianshu.com/p/ae97b692614e?from=timeline JVM体系结构 JVM是一种解释执行class文件的规范技术. JVM体系结构 我翻 ...
- (转载)JVM实现synchronized的底层机制
目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea.本文并不比较synchronized与Loc ...
随机推荐
- CA/B Forum: SSL证书最长有效期最终被定为两年
这项新规将在2018年实施...... 随着CAB Forum第193号投票的通过,SSL行业将拥有更新更短的最长SSL证书有效期. 作为SSL行业的风向标,CAB Forum制定过许多行业规则,及规 ...
- TypeScript入门-基本数据类型
▓▓▓▓▓▓ 大致介绍 TypeScript是由C#语言之父Anders Hejlsberg主导开发的一门编程语言,TypeScript本质上是向JavaScript语言添加了可选的静态类型和基于类的 ...
- 老李分享:接电话扩展之uiautomator 1
老李分享:接电话扩展之uiautomator poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq ...
- Java并发编程:同步锁、读写锁
之前我们说过线程安全问题可以用锁机制来解决,即线程必要要先获得锁,之后才能进行其他操作.其实在 Java 的 API 中有这样一些锁类可以提供给我们使用,与其他对象作为锁相比,它们具有更强大的功能. ...
- z-index用法总结
一.定义: z-index 只适用于元素有定位的情况,表示层级 数值越大 层级越高 展示的位置越靠前. 二.用法: 1.同级关系: z-index值较大的元素将叠加在z-index值较小的元素之上 ( ...
- 第一章:Druid简介
第一章:Druid简介 声明 公司的项目中用到了Druid(不是阿里的连接池),由于网上没有中文的文档,所以只好阅读官方文档.本人第一次阅读英文的文档,非常吃力,借助翻译工具和自己的理解阅读了Duri ...
- Xcode新建python项目
1.找到电脑上安装Python的路径.OSX系统默认安装了python,默认的路径为/usr/bin/python.不确定的情况下,也可以打开命令行,用 whereis python 命令查看 2.打 ...
- HDFS中NameNode启动过程
移动到hadoop文件目录下 NameNode启动命令:sbin/hadoop-daemon.sh start namenode DataNode启动命令:sbin/hadoop-daemon.sh ...
- 打印星号(*)三角形(C# Linq实现)的小例子
以前看面试宝典(C#)的时候,记得有一道题是打印三角形的.比如下图: 记得那时候刚学C#花了我好长时间才做出来,那是用的方法没有使用到linq,现在使用Linq重新做一次.以下是代码: ; ; i & ...
- 一、iOS中的事件可以分为3大类型
触摸事件加速计事件远程控制事件 响应者对象在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件.我们称之为"响应者对象" UIApplica ...