foreach
无论如何在引擎盖下使用迭代器。它实际上只是语法糖。
考虑以下程序:
import java.util.List;
import java.util.ArrayList;
public class Whatever {
private final List<Integer> list = new ArrayList<>();
public void main() {
for(Integer i : list) {
}
}
}
让我们用 编译它javac Whatever.java
,
并读取 的反汇编字节码main()
,使用javap -c Whatever
:
public void main();
Code:
0: aload_0
1: getfield #4 // Field list:Ljava/util/List;
4: invokeinterface #5, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
9: astore_1
10: aload_1
11: invokeinterface #6, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
16: ifeq 32
19: aload_1
20: invokeinterface #7, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
25: checkcast #8 // class java/lang/Integer
28: astore_2
29: goto 10
32: return
我们可以看到foreach
编译成一个程序:
- 使用创建迭代器
List.iterator()
- If
Iterator.hasNext()
: 调用Iterator.next()
并继续循环
至于“为什么这个无用的循环没有从编译后的代码中得到优化?我们可以看到它对列表项没有任何作用”:好吧,您可以编写.iterator()
具有副作用的可迭代对象,或者这样.hasNext()
有副作用或有意义的后果。
你可以很容易地想象一个表示来自数据库的可滚动查询的迭代可能会做一些戏剧性的事情.hasNext()
(比如联系数据库,或者因为你已经到达结果集的末尾而关闭游标)。
因此,即使我们可以证明循环体中没有发生任何事情……但证明在我们迭代时没有发生任何有意义/后果性的事情会更加昂贵(难以处理?)。编译器必须将这个空循环体留在程序中。
我们所希望的最好的结果就是编译器警告。有趣的是,它javac -Xlint:all Whatever.java
并没有警告我们这个空循环体。IntelliJ IDEA 可以。诚然,我已经将 IntelliJ 配置为使用 Eclipse 编译器,但这可能不是原因。