java for底层_foreach底层机制

简单例子

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

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 public classSimple {2

3 public static voidmain(String[] args) {4 int i = 5;5 System.out.println(i);6 }7 }

View Code

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

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

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

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 Compiled from "Simple.java"

2 public classcn._012thDay._foreach.Simple {3 publiccn._012thDay._foreach.Simple();4 Code:5 0: aload_0 将第一个引用型本地变量推送至栈顶;6 1: invokespecial #8 //Method java/lang/Object."":()V

7 调用超类构造方法;8 4: return

9

10 public static voidmain(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 获取类属性,加载入栈,打印栈顶,即523

24 9: return

25 }

View Code

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

第一步:加载超类Object类

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

第三部:获取静态属性

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

第五步:打印

for循环

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 public classForSimple {2

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

View Code

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

javap -c:

Compiled from "ForSimple.java"

public classcn._012thDay._foreach.ForSimple {publiccn._012thDay._foreach.ForSimple();

Code:0: aload_01: invokespecial #8 //Method java/lang/Object."":()V

4: return

public static voidmain(java.lang.String[]);

Code:0: iconst_51: istore_12: goto 15

5: getstatic #16 //Field java/lang/System.out:Ljava/io/PrintStream;

8: iload_19: invokevirtual #22 //Method java/io/PrintStream.println:(I)V

12: iinc 1, -2

15: iload_116: 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[]数组,看看数据是如何存储的:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 public classSimpleDemo {2

3 public static voidmain(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 }

View Code

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

Compiled from "SimpleDemo.java"

public classcn._012thDay._foreach.SimpleDemo {publiccn._012thDay._foreach.SimpleDemo();

Code:0: aload_01: invokespecial #8 //Method java/lang/Object."":()V

4: return

public static voidmain(java.lang.String[]);

Code:0: iconst_31: newarray int

3: astore_14: aload_15: iconst_06: bipush 10

8: iastore9: aload_110: iconst_111: bipush 20

13: iastore14: aload_115: iconst_216: bipush 30

18: iastore19: return}

View Code

前3行创建基本类型(int)数组,长度为3,存入本地引用型变量1

将索引为0的位置压入10并存储

将索引为1的位置压入20并存储

将索引为2的位置压入30并存储

接下来开始遍历,加入for循环:

for (int i = 0; i < arr.length; i++) {

System.out.println(arr[i]);

}

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

Compiled from "SimpleDemo.java"

public classcn._012thDay._foreach.SimpleDemo {publiccn._012thDay._foreach.SimpleDemo();

Code:0: aload_01: invokespecial #8 //Method java/lang/Object."":()V

4: return

public static voidmain(java.lang.String[]);

Code:0: iconst_31: newarray int

3: astore_14: aload_15: iconst_06: bipush 10

8: iastore9: aload_110: iconst_111: bipush 20

13: iastore14: aload_115: iconst_216: bipush 30

18: iastore19: iconst_020: istore_221: goto 36

24: getstatic #16 //Field java/lang/System.out:Ljava/io/PrintStream;

27: aload_128: iload_229: iaload30: invokevirtual #22 //Method java/io/PrintStream.println:(I)V

33: iinc 2, 1

36: iload_237: aload_138: arraylength39: if_icmplt 24

42: return}

View Code

上面代码大家应该不那么陌生了,前面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);

}

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

Compiled from "SimpleDemo.java"

public classcn._012thDay._foreach.SimpleDemo {publiccn._012thDay._foreach.SimpleDemo();

Code:0: aload_01: invokespecial #8 //Method java/lang/Object."":()V

4: return

public static voidmain(java.lang.String[]);

Code:0: iconst_31: newarray int

3: astore_14: aload_15: iconst_06: bipush 10

8: iastore9: aload_110: iconst_111: bipush 20

13: iastore14: aload_115: iconst_216: bipush 30

18: iastore19: aload_1 load local1 :0

20: dup copy21: astore 5 int[] local5 =local123: 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 line52: return}

View Code

以上代码我加入了注释,这里大家应该可以看懂了,不考虑超类加载,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代码

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 public classForeachDemo {2

3 public static voidmain(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 voidprint(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 }

View Code

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

Compiled from "ForeachDemo.java"

public classcn._012thDay._foreach.ForeachDemo {publiccn._012thDay._foreach.ForeachDemo();

Code:0: aload_01: invokespecial #8 //Method java/lang/Object."":()V

4: return

public static voidmain(java.lang.String[]);

Code:0: iconst_31: anewarray #16 //class java/lang/String

4: astore_15: aload_16: dup7: astore 5

9: arraylength10: istore 4

12: iconst_013: istore_314: goto 42

17: aload 5

19: iload_320: aaload21: astore_222: new #16 //class java/lang/String

25: dup26: ldc #18 //String b

28: invokespecial #20 //Method java/lang/String."":(Ljava/lang/String;)V

31: astore_232: getstatic #23 //Field java/lang/System.out:Ljava/io/PrintStream;

35: aload_236: invokevirtual #29 //Method java/io/PrintStream.println:(Ljava/lang/String;)V

39: iinc 3, 1

42: iload_343: iload 4

45: if_icmplt 17

48: aload_149: invokestatic #34 //Method print:([Ljava/lang/String;)V

52: return}

View Code

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值