zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    俺自己可以写点代码了
    学姐,孙哥
    Linux/Unix 常用参数使用说明
    DB2 rollforward 命令使用详解
    DB2 create tablespace
    db2 基础语法
    VMWARE虚拟机不显示主机共享的文件夹解决办法
    DB2创建数据库常用参数详解
    从Linux访问Windows共享目录
    Data Flow >> Source >> Error Output >> Error & Truncation: Ignore Failure, Redirect Now, Fail Component
  • 原文地址:https://www.cnblogs.com/tomasman/p/6751751.html
Copyright © 2011-2022 走看看