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 ...
随机推荐
- 浅谈对java中传参问题的理解
之前用的c/c++比较多,在c/c++中对于传参类型,无外乎就是传值.传引用.传指针这几种.但在java中,由于没有指针类型,其传参的方式也发生了相应的变化.在网上找了找,按我之前的理解,java中传 ...
- React文档翻译系列(一)安装
原文地址:原文 本系列是针对React文档进行的翻译,因为自己在学习react的时候,最开始通过看博客或者论坛等中文资料,有些内容是零零散散的接收,并没有给自己带来很好的效果,所以后来决定把文档的原文 ...
- win8效果的横向布局
有一个月没写过博客了,自己的博客也没有看过,前段时间一直在忙着写代码,公司有一个制漆的产品,与传统纵向布局不一样,要求页面横向布局,类似win8的那种布局效果,最开始,我也没有什么头绪,然后硬着头皮做 ...
- 转载八个最佳Python IDE
八个最佳Python IDE 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs Python是一种功能强大.语言简洁的编程语言.本文向大家推荐8个适合Pyt ...
- Python之路-Linux命令基础(4)
作业一: 1)开启Linux系统前添加一块大小为15G的SCSI硬盘 2)开启系统,右击桌面,打开终端 3)为新加的硬盘分区,一个主分区大小为5G,剩余空间给扩展分区,在扩展分区上划分1个逻辑分区,大 ...
- Java进阶之内存管理与垃圾回收
Java是在JVM所虚拟出的内存环境中运行的.内存分为栈(stack)和堆(heap)两部分.我们将分别考察这两个区域. 栈 在Java中,JVM中的栈记录了线程的方法调用.每个线程拥有一个栈.在某个 ...
- 在IIS上部署你的ASP.NET Core项目
概述 与ASP.NET时代不同,ASP.NET Core不再是由IIS工作进程(w3wp.exe)托管,而是使用自托管Web服务器(Kestrel)运行,IIS则是作为反向代理的角色转发请求到Kest ...
- 深度学习实践系列(3)- 使用Keras搭建notMNIST的神经网络
前期回顾: 深度学习实践系列(1)- 从零搭建notMNIST逻辑回归模型 深度学习实践系列(2)- 搭建notMNIST的深度神经网络 在第二篇系列中,我们使用了TensorFlow搭建了第一个深度 ...
- Hibernate基础学习(四)—对象-关系映射(上)
一.映射对象标识符 Java语言按内存地址来识别或区分同一个类的不同对象,而关系数据库按主键值来识别或区分同一个表的不同记录.Hibernate使用对象标识符(OID)来建立内存中的对象和数 ...
- POI框架实现创建Excel表、添加数据、读取数据
public class TestPOI2Excel {//创建2003版本Excel用此方法 @Test public void testWrite03Excel() throws Exceptio ...