foreach底层机制

Posted Tomas曼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了foreach底层机制相关的知识,希望对你有一定的参考价值。

简单例子

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

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

找到其字节码文件所在目录并在目录下打开终端(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 }
View Code

如果不懂指令意思,可查询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 }
View Code

其实这个例子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 }
View Code
技术分享
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
}
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]);
        }

技术分享
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
}
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);
        }

技术分享
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
}
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代码

技术分享
 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 }
View Code
技术分享
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
}
View Code

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

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





以上是关于foreach底层机制的主要内容,如果未能解决你的问题,请参考以下文章

C#VS快捷键

C#VS快捷键

C#VS快捷键

C#VS快捷键

实用代码片段

方便调试使用的代码片段