简单例子
直接了解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