正文
一个简单的 for each
操作
我们写一个简单的 Main.java
来对数组进行 for each
操作(Main.java
的内容如下)
public class Main {
public int f(int[] nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}
return sum;
}
}
执行如下命令可以看到字节码中的内容
javac Main.java
javap -cp . -p -v 'Main'
完整的内容较长,和 f(...)
直接相关的部分如下
public int f(int[]);
descriptor: ([I)I
flags: ACC_PUBLIC
Code:
stack=2, locals=7, args_size=2
0: iconst_0
1: istore_2
2: aload_1
3: astore_3
4: aload_3
5: arraylength
6: istore 4
8: iconst_0
9: istore 5
11: iload 5
13: iload 4
15: if_icmpge 35
18: aload_3
19: iload 5
21: iaload
22: istore 6
24: iload_2
25: iload 6
27: iadd
28: istore_2
29: iinc 5, 1
32: goto 11
35: iload_2
36: ireturn
LineNumberTable:
line 3: 0
line 4: 2
line 5: 24
line 4: 29
line 7: 35
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 11
locals = [ class Main, class "[I", int, class "[I", int, int ]
stack = []
frame_type = 248 /* chop */
offset_delta = 23
内容有点多,还是借助 cfr 来分析吧。
以 cfr-0.148.jar
为例,执行如下命令,就能看到编译器操作之后的 for each
是什么样子了。
java -jar cfr-0.148.jar Main --arrayiter false
执行结果如下
/*
* Decompiled with CFR 0.148.
*/
public class Main {
public int f(int[] arrn) {
int n = 0;
int[] arrn2 = arrn;
int n2 = arrn2.length;
for (int i = 0; i < n2; ++i) {
int n3 = arrn2[i];
n += n3;
}
return n;
}
}
由此可见,编译器会自动将针对数组的 for each
操作转化为普通的 for
循环。
有了 cfr
的帮助,我来尝试一下解释 f(...)
对应的字节码指令。
行号 | 字节码指令 | 简要的解释 |
---|---|---|
0 | iconst_0 |
将常数0 加载到操作数栈栈顶 |
1 | istore_2 |
将操作数栈栈顶的值保存到2 号slot
|
以上指令与如下 java
代码对应
int n = 0;
行号 | 字节码指令 | 简要的解释 |
---|---|---|
2 | aload_1 |
将1 号slot 的值加载到操作数栈栈顶 |
3 | astore_3 |
将操作数栈栈顶的值保存到3 号slot
|
以上指令与如下 java
代码对应
int[] arrn2 = arrn;
行号 | 字节码指令 | 简要的解释 |
---|---|---|
4 | aload_3 |
将3 号slot 中的值加载到操作数栈栈顶 |
5 | arraylength |
操作数栈栈顶的元素是一个数组的引用,求出这个数组的 length
|
6 | istore 4 |
将操作数栈栈顶的值保存到4 号slot
|
以上指令与如下 java
代码对应
int n2 = arrn2.length;
行号 | 字节码指令 | 简要的解释 |
---|---|---|
8 | iconst_0 |
将常数0 加载到操作数栈栈顶 |
9 | istore 5 |
将操作数栈栈顶的值保存到5 号slot
|
以上指令与如下 java
代码中的 int i = 0
对应
for (int i = 0; i < n2; ++i) {
int n3 = arrn2[i];
n += n3;
}
行号 | 字节码指令 | 简要的解释 |
---|---|---|
11 | iload 5 |
将5 号slot 中int 值(也就是i 的值)加载到操作数栈栈顶 |
13 | iload 4 |
将4 号slot 中int 值(也就是n2 的值)加载到操作数栈栈顶 |
15 | if_icmpge 35 |
如果栈顶的int 值大于或等于??则跳转到行号为 35 的地方 |
18 | aload_3 |
将3 号slot 中引用值加载到操作数栈栈顶 |
19 | iload 5 |
将5 号slot 中int 值加载到操作数栈栈顶 |
21 | iaload |
|
22 | istore 6 |
将栈顶的int 值保存到6 号slot 中 |
24 | iload_2 |
将2 号slot 中int 值加载到操作数栈栈顶 |
25 | iload 6 |
将6 号slot 中int 值加载到操作数栈栈顶 |
27 | iadd |
将栈顶的两个元素弹出, 再将这两个元素的和保存到栈顶 |
28 | istore_2 |
将栈顶的int 值保存至2 号slot
|
29 | iinc 5, 1 |
将5 号slot 中的int 值增加1
|
32 | goto 11 |
跳转到行号为11 的字节码指令那里 |
35 | iload_2 |
将2 号slot 中int 值加载到操作数栈栈顶 |
36 | ireturn |
将栈顶的int 值返回 |
剩余部分尚未写完
参考文章
- 14.14.2. The enhanced for statement : https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.14.2
-
How does the Java 'for each' loop work? 以及该问题下的一个回答
https://stackoverflow.com/a/85206