简单例子

直接了解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底层机制的更多相关文章

  1. [转]STL 容器一些底层机制

    1.vector 容器 vector 的数据安排以及操作方式,与 array 非常相似.两者的唯一区别在于空间的运用的灵活性.array 是静态空间,一旦配置了就不能改变,vector 是动态数组.在 ...

  2. C++ STL容器底层机制

    1.vector容器 vector的数据安排以及操作方式,与array非常相似.两者的唯一区别在于空间的运用的灵活性.array是静态空间,一旦配置了就不能改变.vector是动态空间,随着元素的加入 ...

  3. 探索C++的底层机制

    探索C++的底层机制 在看这篇文章之前,请你先要明白一点:那就是c++为我们所提供的各种存取控制仅仅是在编译阶段给我们的限制,也就是说是编译器确保了你在完成任务之前的正确行为,如果你的行为不正确,那么 ...

  4. tensorflow入门教程和底层机制简单解说——本质就是图计算,自动寻找依赖,想想spark机制就明白了

    简介 本章的目的是让你了解和运行 TensorFlow! 在开始之前, 让我们先看一段使用 Python API 撰写的 TensorFlow 示例代码, 让你对将要学习的内容有初步的印象. 这段很短 ...

  5. 20191031:Python底层机制

    20191031:Python底层机制 python底层从3个方面来说,分别是: 引用计数机制 垃圾回收机制 内存池机制 引用计数机制 使用引用计数来追踪内存中的对象,所有对象都有引用计数,并且这个引 ...

  6. php-浅谈php底层机制

    php-浅谈php底层机制 1. PHP的设计理念及特点 多进程模型:由于PHP是多进程模型,不同请求间互不干涉,这样保证了一个请求挂掉不会对全盘服务造成影响,当然,随着时代发展,PHP也早已支持多线 ...

  7. ②NuPlayer播放框架之ALooper-AHandler-AMessage底层机制分析

    [时间:2016-09] [状态:Open] [关键词:android,NuPlayer,开源播放器,播放框架,ALooper,AHandler,AMessage] 前文中提到过NuPlayer基于S ...

  8. Java 底层机制(JVM/堆/栈/方法区/GC/类加载)

    转载:https://www.jianshu.com/p/ae97b692614e?from=timeline JVM体系结构 JVM是一种解释执行class文件的规范技术.   JVM体系结构 我翻 ...

  9. (转载)JVM实现synchronized的底层机制

    目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea.本文并不比较synchronized与Loc ...

随机推荐

  1. Frame框架

    框架 frameset    框架集    如果使用框架集,当前页面不能有body    cols="300,*" :左右拆分,左边宽300,右边宽剩余    rows=" ...

  2. 彻底理解容器类(2)------- AbstractCollection深入了解

    h2 { background-color: Skyblue } AbstractCollection认识 AbstractCollection是Collection接口的抽象实现.实现了一部分Col ...

  3. js鼠标滑轮侧边广告(仅IE可用)

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  4. PRINCE2有用吗?

    PRINCE2项目认证--在欧美国际久负盛名,在国内近来才逐渐为业内人士所了解.PRINCE2认证2007年首入中国,目前国内参加培训并获取认证的专业人士不足五万人,PRINCE2全称为"受 ...

  5. PRINCE2考试一共多少道题

    一.Foundation 基础级: 考试时长 1 个小时: 75 道单选题,其中 5 道随机测试题,无论对错都不计入考分:满分 70 分,获得 35 分才能通过考试,正确率 50%: 全程闭卷考试 二 ...

  6. 【C++】浅谈三大特性之一继承(二)

    三,继承方式&访问限定符 派生类可以继承基类中除了构造函数和析构函数之外的所有成员,但是这些成员的访问属性是由继承方式决定的. 不同的继承方式下基类成员在派生类中的访问属性: 举例说明: (1 ...

  7. Java基础—JDK环境变量配置

    1.安装JDK 下载网址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 注意点 ...

  8. 每天学点Java小知识【1】

    一 Java标识符和关键字 1.标识符 作用:用来标识类名.变量名.方法名.类型名.数组名.文件名的有效字符序列. 组成规则:由字母.下划线.美元符号和数字组成,且第一个字符不能是数字字符.注意:标识 ...

  9. 查找及修改日程管理系统中的bug

    1.Java没有分包都糅合在一起 2.在登录界面,没有输入账号密码,点击登录按钮时程序会崩溃,需要给给用户名和密码添加Toast (注:由于程序没有运行出来,所以是搭档孙刘兰的截图) 3.Regist ...

  10. android开发之-数据存储Map、HashMap、Hashtable、concurenthashmap区别

    选择一个map进行软件基础数据初始化操作,要求第一次初始化后,不修改数据,可能会出现静态类被回收,然后在进行初始化操作? 1.Map :接口 /** * A {@code Map} is a data ...