Java 是如何支持 for each 方式的循环的
结论
- 如果
for each的对象是一个数组,那么javac编译器会添加以下逻辑。
- 获取数组长度
- 用下标遍历数组
- 如果
for each的对象是一个Iterable实例,那么javac编译器会添加以下逻辑。
- 获取
iterator - 调用
iterator的hasNext()/next()方法,从而遍历这个Iterable实例。
代码
Java Language Specification 14.14.2. The enhanced for statement 中提到了如下内容
可见 java 中的 for each 循环支持数组和 Iterable 对象两种情况。
我们分别来进行探索。
数组
我们将以下代码保存在 ArrayExample.java 里。
public class ArrayExample {
int sum(int[] array) {
int ans = 0;
for (int element : array) {
ans += element;
}
return ans;
}
}
用如下的命令可以编译 ArrayExample.java。
javac -g -parameters ArrayExample.java
编译后,会生成 ArrayExample.class。
使用如下命令可以查看这个 class 文件的内容。
javap -v -p ArrayExample
和 traverse(int[]) 方法相关的部分如下
int sum(int[]);
descriptor: ([I)I
flags: (0x0000)
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
LocalVariableTable:
Start Length Slot Name Signature
24 5 6 element I
0 37 0 this LArrayExample;
0 37 1 array [I
2 35 2 ans I
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 11
locals = [ class ArrayExample, class "[I", int, class "[I", int, int ]
stack = []
frame_type = 248 /* chop */
offset_delta = 23
MethodParameters:
Name Flags
array
从以上内容不容易直接看出来对应的 java 代码是什么样子。
我们换个思路,可以在 ArrayExample.java 中加入如下的代码,重新编译,然后对比 sum(int[]) 和 sum2(int[]) 的 code 数组(code 数组在 Code 属性 中)。
int sum2(int[] array) {
int ans = 0;
int[] temp = array;
int len = temp.length;
for (int i = 0;i < len;i++) {
int element = temp[i];
ans += element;
}
return ans;
}
根据我的对比,sum(int[]) 和 sum2(int[]) 的 code 数组是一样的(LocalVariableTable 属性有差异,因为我用了 temp,len 这样的局部变量),所以可以认为 sum(int[]) 和 sum2(int[]) 是等效的。
由此可见,如果 for each 循环的对象是一个数组,那么 javac 编译器会自动补上获取数组长度,遍历数组之类的逻辑。
Iterable 对象
我们将以下代码保存在 IterableExample.java 中。
import java.util.List;
public class IterableExample {
int sum(List<Integer> list) {
int ans = 0;
for (int element : list) {
ans += element;
}
return ans;
}
}
以下命令可以编译 IterableExample.java。
javac -g -parameters IterableExample.java
编译后,会生成 IterableExample.class 文件,使用以下命令可以查看这个 class 文件的内容。
javap -v -p IterableExample
和 sum(java.util.List<java.lang.Integer>) 方法相关的部分如下。
int sum(java.util.List<java.lang.Integer>);
descriptor: (Ljava/util/List;)I
flags: (0x0000)
Code:
stack=2, locals=5, args_size=2
0: iconst_0
1: istore_2
2: aload_1
3: invokeinterface #7, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
8: astore_3
9: aload_3
10: invokeinterface #13, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
15: ifeq 40
18: aload_3
19: invokeinterface #19, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
24: checkcast #23 // class java/lang/Integer
27: invokevirtual #25 // Method java/lang/Integer.intValue:()I
30: istore 4
32: iload_2
33: iload 4
35: iadd
36: istore_2
37: goto 9
40: iload_2
41: ireturn
LineNumberTable:
line 5: 0
line 6: 2
line 7: 32
line 8: 37
line 9: 40
LocalVariableTable:
Start Length Slot Name Signature
32 5 4 element I
0 42 0 this LIterableExample;
0 42 1 list Ljava/util/List;
2 40 2 ans I
LocalVariableTypeTable:
Start Length Slot Name Signature
0 42 1 list Ljava/util/List<Ljava/lang/Integer;>;
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 9
locals = [ int, class java/util/Iterator ]
frame_type = 250 /* chop */
offset_delta = 30
MethodParameters:
Name Flags
list
Signature: #48 // (Ljava/util/List<Ljava/lang/Integer;>;)I
由于 code 数组(code 数组在 Code 属性 中)的内容有些多,直接转化为对应的 java 代码不太容易。
我们可以尝试另写一个等效的方法 sum2(List<Integer>)。在编译后,如果 sum(List<Integer>) 和 sum2(List<Integer>) 有相同的 code 数组,那就可以认为两个方法是等效的。
我把 IterableExample.java 修改成了如下的样子,sum(List<Integer>) 和 sum2(List<Integer>) 有相同的 code 数组。
import java.util.List;
import java.util.Iterator;
public class IterableExample {
int sum(List<Integer> list) {
int ans = 0;
for (int element : list) {
ans += element;
}
return ans;
}
int sum2(List<Integer> list) {
int ans = 0;
Iterator iter = list.iterator();
while (iter.hasNext()) {
int element = ((Integer) iter.next()).intValue();
ans += element;
}
return ans;
}
}
由此可见,当 for each 的对象是 Iterable 对象时,javac 编译器会自动加上获取 iterator,以及利用 iterator 来进行遍历的逻辑。