简单例子

直接了解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. 自己开发图表插件,脱离echart

    前言 由于公司业务需要做一些图标来展示一些数据,之前都是用百度的echart.js.这次放弃使用它转而自己开发是有几个原因1.echart文件太大,有些功能用不到2.echart样式不易扩展3.需求简 ...

  2. 微信sdk

    <!DOCTYPE html> <html> <head>     <meta charset="utf-8">     <t ...

  3. unity插件开发——Selection

    Selection是一个静态类,它的主要功能是获取在project窗口中鼠标选择的对象或者是inspector窗口的当前显示的对象.它的字段有 activeGameObject activeInsta ...

  4. python学习随笔(二)

    今天主要是用户输入输出,字符拼接. 在python中,多行注释是3个单引号或双引号 单行注释是#号开头,如下. ''' name = "xiaoming" print(name) ...

  5. ABP框架实战 1.基础信息维护

    在之前的一个开发项目中,因为公司战略发展,引用了这个ABP开源框架作为新项目的基础版本,由于客户的要求需要迁移旧系统数据,以及其他的一些原因,数据库采用了Oracle数据库管理.所以引用了Dapper ...

  6. 手机自动化测试:搭建appium手机自动化测试开发环境

    手机自动化测试:搭建appium手机自动化测试开发环境   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大 ...

  7. 测试开发Python培训:自动发布新浪微博-技术篇

    测试开发Python培训:自动发布新浪微博-技术篇   在前面我们教大家如何登陆,大家需要先看自动登陆新浪微博(http://www.cnblogs.com/laoli0201/articles/48 ...

  8. Java中利用BigInteger类进行大数开方

    在Java中有时会用到大数据,基本数据类型的存储范围已经不能满足要求了,如要对10的1000次方的这样一个数据规模的数进行开方运算,很明显不能直接用Math.sqrt()来进行计算,因为已经溢出了. ...

  9. java中GUI的awt和Swing的知识点

    刚刚学习了java的GUI,写了几个程序,基本熟悉了awt和Swing,下面和大家分享一下知识点 1.JFrame的层次结构 参考:http://tieba.baidu.com/p/200421612 ...

  10. sql中如何分割字符串

    使用方式: SELECT AllItem AS BldGUID  FROM dbo.fn_split('01.02.03','.') 函数:   GO )) )) --实现split功能 的函数 as ...