出自于:https://www.cnblogs.com/tomasman/p/6751751.html

直接了解foreach底层有些困难,我们需要从更简单的例子着手.下面上一个简单例子:

1 public class Simple {
2
3 public static void main(String[] args) {
4 int i = 5;
5 System.out.println(i);
6 }
7 }

找到其字节码文件所在目录并在目录下打开终端(Windows系统是在目录下shift+鼠标右键选择在此打开powershell窗口)

输入指令:javac -Simple.class >SimpleRunc

目录中得到SimpleRunc文件,打开它,会看到如下代码(里面有我的注释):

 1 Compiled from "Simple.java"
2 public class cn._012thDay._foreach.Simple {
3 public cn._012thDay._foreach.Simple();
4 Code:
5 0: aload_0 将第一个引用型本地变量推送至栈顶;
6 1: invokespecial #8 // Method java/lang/Object."<init>":()V
7 调用超类构造方法;
8 4: return
9
10 public static void main(java.lang.String[]);
11 Code:
12 0: iconst_5 将int型5推送至栈顶;
13 1: istore_1 将栈顶int型数据存入第二个本地变量;
14 此处推测:第一个本地变量是超类;
15
16
17 2: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
18 获取本地静态域并将其压入栈顶;即获取静态属性压入栈顶
19 5: iload_1 将第二个int型本地变量推送至栈顶;
20 6: invokevirtual #22 // Method java/io/PrintStream.println:(I)V
21 调用实例方法;
22 获取类属性,加载入栈,打印栈顶,即5
23
24 9: return
25 }

如果不懂指令意思,可查询JVM指令表.这里我说明一下步骤:

第一步:加载超类Object类

第二步:将int类型的5压入栈顶,然后将5存入本地变量1

第三部:获取静态属性

第四步:加载本地变量1,即将5推送至栈顶

第五步:打印

for循环

1 public class ForSimple {
2
3 public static void main(String[] args) {
4 for (int i = 5; i > 0; i-=2) {
5 System.out.println(i);
6 }
7 }
8 }

其实这个例子foreach就做不了,因为foreach遍历必须要有一个数组.

javap -c:

Compiled from "ForSimple.java"
public class cn._012thDay._foreach.ForSimple {
public cn._012thDay._foreach.ForSimple();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: iconst_5
1: istore_1
2: goto 15
5: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
8: iload_1
9: invokevirtual #22 // Method java/io/PrintStream.println:(I)V
12: iinc 1, -2
15: iload_1
16: ifgt 5
19: 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[]数组,看看数据是如何存储的:

 1 public class SimpleDemo {
2
3 public static void main(String[] args) {
4 // TODO Auto-generated method stub
5 int[]arr = new int[3];
6 arr[0] = 10;
7 arr[1] = 20;
8 arr[2] = 30;
9 }
10 }
Compiled from "SimpleDemo.java"
public class cn._012thDay._foreach.SimpleDemo {
public cn._012thDay._foreach.SimpleDemo();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: iconst_3
1: newarray int
3: astore_1
4: aload_1
5: iconst_0
6: bipush 10
8: iastore
9: aload_1
10: iconst_1
11: bipush 20
13: iastore
14: aload_1
15: iconst_2
16: bipush 30
18: iastore
19: 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:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: iconst_3
1: newarray int
3: astore_1
4: aload_1
5: iconst_0
6: bipush 10
8: iastore
9: aload_1
10: iconst_1
11: bipush 20
13: iastore
14: aload_1
15: iconst_2
16: bipush 30
18: iastore
19: iconst_0
20: istore_2
21: goto 36
24: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
27: aload_1
28: iload_2
29: iaload
30: invokevirtual #22 // Method java/io/PrintStream.println:(I)V
33: iinc 2, 1
36: iload_2
37: aload_1
38: arraylength
39: if_icmplt 24
42: 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:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: iconst_3
1: newarray int
3: astore_1
4: aload_1
5: iconst_0
6: bipush 10
8: iastore
9: aload_1
10: iconst_1
11: bipush 20
13: iastore
14: aload_1
15: iconst_2
16: bipush 30
18: iastore
19: aload_1 load local1 :0
20: dup copy
21: astore 5 int[] local5 = local1
23: arraylength 3
24: istore 4 int local4 = 3
26: iconst_0 0
27: istore_3 int local3 = 0
28: goto 46
31: aload 5 load local5 : int[3]
33: iload_3 load local3 : 0..
34: iaload arr[0..]进栈
35: istore_2 int local2 = arr[0..]
36: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
39: iload_2 load local2 :arr[0..]
40: invokevirtual #22 // Method java/io/PrintStream.println:(I)V
43: iinc 3, 1 local3 +=1
46: iload_3 load local3 :0..
47: iload 4 load local4 :3
49: if_icmplt 31 local3 < local4 ? go line31:next line
52: 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代码

 1 public class ForeachDemo {
2
3 public static void main(String[] args) {
4 // TODO Auto-generated method stub
5
6 String[] s1 = new String[3];
7 for (String item : s1) {//这里的s1实际是s1副本
8 item = new String("b");
9 System.out.println(item);//这里可以把副本中的每项打印出来
10 }
11 print(s1);//打印s1是null,因为s1在内存地址中没有任何变化
12
13 }
14
15 private static void print(String[] s) {
16 // TODO Auto-generated method stub
17 for (int i = 0; i < s.length; i++) {
18 System.out.print(s[i]+" ");
19 }
20 }
21
22 }
Compiled from "ForeachDemo.java"
public class cn._012thDay._foreach.ForeachDemo {
public cn._012thDay._foreach.ForeachDemo();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: iconst_3
1: anewarray #16 // class java/lang/String
4: astore_1
5: aload_1
6: dup
7: astore 5
9: arraylength
10: istore 4
12: iconst_0
13: istore_3
14: goto 42
17: aload 5
19: iload_3
20: aaload
21: astore_2
22: new #16 // class java/lang/String
25: dup
26: ldc #18 // String b
28: invokespecial #20 // Method java/lang/String."<init>":(Ljava/lang/String;)V
31: astore_2
32: getstatic #23 // Field java/lang/System.out:Ljava/io/PrintStream;
35: aload_2
36: invokevirtual #29 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
39: iinc 3, 1
42: iload_3
43: iload 4
45: if_icmplt 17
48: aload_1
49: invokestatic #34 // Method print:([Ljava/lang/String;)V
52: return
}

javap -c代码第7行新建了String[]数组副本变量5,之后一直在对副本进行操作,直到48行aload_1,然后打印,此时不难看出foreach中进行的所有操作都没有对本地变量1(即原数组)的值产生任何影响.

所以main方法最后一行打印数组s1,其结果一定是3个null

如何查看.java文件的字节码(原码)的更多相关文章

  1. java 变量及数据类型、原码、反码、补码

    Java基础——变量及数据类型 变量的概念 内存中的一个存储区域 变量名+数据类型 可在同一类型范围内不断变化 为什么定义变量: 用于不断的存放同一类型的常量,并可以重复使用 使用变量注意: 变量的作 ...

  2. 命令查看java的class字节码文件、verbose、synchronize、javac、javap

    查看Java字节码 1 javac –verbose查看运行类是加载了那些jar文件 HelloWorld演示: public class Test { public static void main ...

  3. 命令查看java的class字节码文件

    源代码: public class Math { public static void main(String[] args){ int a=1; int b=2; int c=(a+b)*10; } ...

  4. 查看Java文件对应的字节码

    1. 编译为class文件:javac 2. 使用javap查看bytecode:javap -v

  5. 关于JAVA文件的字节转字符练习

    PrintWriter向文件写入字符,接收Writer对象.BufferedWriter是Writer对象还具有缓冲作用让写入更加高效,同时最重要的是BufferedWriter接 收转换流对象Fil ...

  6. JAVA 文件转字节数组转字符串

    public static void main(String[] args) throws IOException { byte[] bytes = FileUtils.readFileToByteA ...

  7. 在Myeclipse下查看Java字节码指令信息

         在实际项目开发中,有时为了了解Java编译器内部的一些工作,需要查看Java文件对应的具体的字节码指令集,这里提供两种方式供参考. 一.使用javap命令      javap是JDK提供的 ...

  8. 用Eclipse插件Bytecode Outline来查看Java字节码

    在遇到一些小问题的时候我们经常会使用Javap反编译取得字节码来分析,虽然Javap能完成这个工作,但是有两个缺点,一方面操作麻烦,需要很多步骤,一方面没有文档注释,对新手来说看起字节码来比较麻烦. ...

  9. 二进制原码、反码、补码以及Java中的<< 和 >> 和 >>> 详细分析

    1.计算机二进制系统中最小单位bit 在计算机二进制系统中: bit (位) :数据存储的最小单元. 简记为b,也称为比特(bit),每个二进制数字0或1就是一个位(bit),其中,每 8bit = ...

随机推荐

  1. C# 递归缩小图片

    需求:图片太大,上传到服务器会非常占用服务器空间,而系统又不要求高清图片,于是就通过递归的方式让图片每次减少10%的大小,当图片大小小于100k的时候就保存在本地,核心代码如下: class Prog ...

  2. 无法访问SVN历史记录的问题

      今天在eclipse中发现无法访问SVN的历史记录,提示条目不可读,截图如下: 用小乌龟客户端试了试也不行,截图如下: 最后解决办法是在SVN服务器上将代码仓库中conf目录下的svnserve. ...

  3. linux下编译C/C++ 程序

    C/C++的速度是Python和perl所无法比拟的,尤其对于处理超大的生物信息学文件来说. 最近在写一个最简单的fastq cut工具,Python简直慢到不能忍,8G的fastq.gz文件的cut ...

  4. 20170906xlVBA_GetEMailFromDocument

    Public Sub GetDataFromWord() AppSettings 'On Error GoTo ErrHandler Dim StartTime, UsedTime As Varian ...

  5. Android的组件化和模块化

    Android随着业务的增多,而且后续新的需求的增加,代码的修改会变得非常频繁 然后最近在看组件化和模块化 公司的业务没有那么大,所以这种方式我并没有采取 但是还是需要了解下他的使用机制 还有优缺点之 ...

  6. android-------- 强引用、软引用、弱引用、虚引用使用

    在Java中,虽然不需要程序员手动去管理对象的生命周期,但是如果希望某些对象具备一定的生命周期的话(比如内存不足时JVM就会自动回收某些对象从而避免OutOfMemory的错误)就需要用到软引用和弱引 ...

  7. Confluence 6 空间

    什么是一个空间? Confluence 空间是包含有页面和博客页面的容器.你也可以将空间认为是对你工作中可以使用的 2 中类型的目录. 在 Confluence 中有下面 2 种空间类型: 站点空间( ...

  8. DP 传球问题

    洛谷P1057 传球问题 分析:经过m次传球到第i个人的方法可以由经过m-1次传球到第i个人和到第i-1个人传递得来 设dp[i][j]为经过j次传球后到达第i个人的方法数,可得到状态转移方程为: d ...

  9. 第二阶段——个人工作总结DAY06

    1.昨天做了什么:昨天做完了修改密码的界面.(有点丑) 2.今天打算做什么:今天制作时间轴. 3.遇到的困难:无.

  10. ubuntu计划任务的编写

    1,首先,我编写的计划任务是在ubunut系统上的.首先我们来认识一下每一个 * 这样子的符号代表什么意思吧! *  *  *  *  * 从左到右: 第1列表示分钟1-59 每分钟用*或者 */1表 ...