定义“for-each循环”,最好使用这个约定:
案例1
for (String s : xxx.getList())
或者这个:
案例2
List<String> list = xxx.getList();
for (String s : list)
在案例1中,调用方法getList()的次数是多少? 每次循环一次或在开始时只有一次?
由于
答案 0 :(得分:3)
永远不要花时间考虑这种类型的优化。
1)如果你能想到一个看似更有效但功能相同的单行更改,那么确定编写JIT编译器的人也会想到它。并且它们将编译为相同的字节码。
2)除了最关键的循环外,可读性远远超过性能。只有当您打算将列表用于for循环范围之外的其他内容时,才应使用case 2,并且for循环会改变列表。在这种情况下,携带更改的列表比重复调用xxx.getList()在语义上更清晰,因为查看函数的一部分的程序员可能没有意识到您已在相同函数中进一步编辑了xxx中的列表。
3)在这种类型的编程中几乎没有硬性规则和快速规则,以及关于代表什么和可读性的观点&#34;而变化。
请记住,编译语言与解释语言不同,您编写的代码不是处理器的文字脚本。几乎所有你能想象到的小优化,以及很多你不能想象的优化都将由编译器完成。此外,当尝试优化它时,编译器可以优化许多高复杂度的标准编程模式。你自己将只会导致编译器“混淆不清”,而不是认识到这是一种标准模式,并且无法在应该的时候进行优化。
========================
在评论中,slim指出了使用案例2的另一种情况 - 如果你有几个for循环,并且getList是一个昂贵的操作,它根据请求构造列表,而不是只返回一个现有的对象。
答案 1 :(得分:1)
你可以这样测试:
public static void main(tring[] args) {
for (String string : getList()) {
System.out.println(string);
}
}
private static List<String> getList() {
System.out.println("getList");
List<String> l = new ArrayList<String>();
l.add("a");
l.add("b");
return l;
}
...你会发现getList()只被调用一次。
增强的for()循环的语法是:
for(Type item : iterable) ...
在您的示例中,getList()
在运行时返回Iterable
- 在本例中为List
。一旦完成,循环就可以使用它所需的Iterable。
for (String string : getList()) ...
和
List list = getList();
for (String string : list) ...
......是等价的。第一种形式具有短而清晰的优点。第二种形式的优点是,如果需要,您可以在之后再次使用列表。
在Eclipse中,您可以使用自动重构在两种表单之间切换(其他IDE具有相似的功能):
从第一个表单开始,选择getList()
,右键单击,Refactor
- &gt; extract local variable
。 Eclipse将提示您输入变量名称。输入list
,它将为您创建第二个表单。
从第二个表单开始,在list
语句中选择for()
,右键单击,Refactor
- &gt; Inline
。它会提示,然后将其改回旧格式。
重构应该导致功能相同的代码,因此您可以使用它作为两种形式等效的证据。
但要小心;其他循环形式并不那么聪明。
while( size < file.length()) {
...
}
...每次循环迭代时执行file.length()
,而
long fileLength = file.length();
while( size < fileLength ) {
...
}
...只执行一次file.length()
。这同样适用于传统的for(;;)
循环。
上面描述的Eclipse重构转换也会在这两种形式之间切换,但行为却不一样。
答案 2 :(得分:0)
在每种情况下,getList方法的调用次数相同 - 只有一次。
至于采用哪种风格,取决于。如果您需要在for循环后再次引用列表,请转到案例2以避免调用getList超出您的需要。
从调试的角度来看,我也更喜欢Case 2。您可以在for循环上设置断点,并在迭代开始之前检查列表的内容。
答案 3 :(得分:0)
增强的for循环使用提供的Iterable
(或数组)来获取Iterator
来循环项目。 Iterator
只获得一次。
所以
for (String s : list) {
// loop code
}
与:
相同Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
String s = iter.next();
// loop code
}
提示:您可以在增强型for循环中编写和使用自己的Iterable实现。
答案 4 :(得分:0)
for (String s : list)
编译后
for (Iterator<String> i = list.iterator(); i.hasNext(); ){...}
for (String s : xxx.getList()){...}
编译后会是 -
for (Iterator<String> i = xxx.getList().iterator(); i.hasNext(); ){...}
这意味着第二种情况也会调用单次getList
。
答案 5 :(得分:0)
在case 1中,getList()方法只被调用一次。您可以在调用方法时通过打印来验证这一点。
除了人们已经提供的答案之外,我们还可以查看两个for-loop样式生成的字节代码。
对于此代码:
List<String> l = getList();
for (String s : l) {
processItem(s);
}
字节代码是:
0: invokestatic #4; //Method getList:()Ljava/util/List;
3: astore_1
4: aload_1
5: invokeinterface #5, 1; //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
10: astore_2
11: aload_2
12: invokeinterface #6, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z
17: ifeq 37
20: aload_2
21: invokeinterface #7, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
26: checkcast #8; //class java/lang/String
29: astore_3
30: aload_3
31: invokestatic #9; //Method processItem:(Ljava/lang/String;)V
34: goto 11
37: return
对于这段代码:
for (String s : getList()) {
processItem(s);
}
字节代码是:
0: invokestatic #4; //Method getList:()Ljava/util/List;
3: invokeinterface #5, 1; //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
8: astore_1
9: aload_1
10: invokeinterface #6, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z
15: ifeq 35
18: aload_1
19: invokeinterface #7, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
24: checkcast #8; //class java/lang/String
27: astore_2
28: aload_2
29: invokestatic #9; //Method processItem:(Ljava/lang/String;)V
32: goto 9
35: return
两个字节代码之间的唯一区别是在前一个字节代码中,我们有:
3: astore_1
4: aload_1
对应于将getList()
方法返回的结果存储在变量中。换句话说,两个for循环样式具有几乎相同的性能(除了用于存储getList()
方法返回的结果的变量)。