当我进行函数调用时,我试图找到for-each循环的工作。请参阅以下代码,
public static int [] returnArr()
{
int [] a=new int [] {1,2,3,4,5};
return a;
}
public static void main(String[] args)
{
//Version 1
for(int a : returnArr())
{
System.out.println(a);
}
//Version 2
int [] myArr=returnArr();
for(int a : myArr)
{
System.out.println(a);
}
}
在版本1中,我在for-each循环中调用returnArr()方法,在版本2中,我显式调用returnArr()方法并将其分配给数组,然后迭代它。两种方案的结果相同。我想知道哪个更有效,为什么。
我认为版本2会更有效率,因为我没有在每次迭代中调用方法。但令我惊讶的是,当我使用版本1调试代码时,我看到方法调用只发生一次!
任何人都可以解释它是如何实际工作的?当我编写复杂对象代码时,哪个更有效/更好?
答案 0 :(得分:5)
Java Language Specification显示基础编译
让
L1 ... Lm
立即成为(可能为空)标签序列 在增强的for语句之前。增强的for语句相当于基本的
for
语句 形式:T[] #a = Expression; L1: L2: ... Lm: for (int #i = 0; #i < #a.length; #i++) { {VariableModifier} TargetType Identifier = #a[#i]; Statement }
其中Expression
是增强型for语句:
的右侧(您的returnArr()
)。在这两种情况下,它只被评估一次:在版本1中,作为增强的for语句的一部分;在版本2中,因为它的结果被分配给一个变量,然后在增强的for语句中使用它。
答案 1 :(得分:1)
编译器只调用方法 returnArr()
一次。 编译时优化:)
字节代码:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=6, args_size=1
** case -1 start ***
0: invokestatic #20 // Method returnArr:()[I --> called only once.
3: dup
4: astore 4
6: arraylength
7: istore_3
8: iconst_0
9: istore_2
10: goto 28
13: aload 4 --> loop start
15: iload_2
16: iaload
17: istore_1
18: getstatic #22 // Field java/lang/System.out:Ljav
/io/PrintStream;
21: iload_1
22: invokevirtual #28 // Method java/io/PrintStream.prin
ln:(I)V
25: iinc 2, 1
28: iload_2
29: iload_3
30: if_icmplt 13
***case -2 start****
33: invokestatic #20 // Method returnArr:()[I
36: astore_1
37: aload_1
38: dup
39: astore 5
41: arraylength
42: istore 4
44: iconst_0
45: istore_3
46: goto 64
49: aload 5 --> loop start case 2
51: iload_3
52: iaload
53: istore_2
54: getstatic #22 // Field java/lang/System.out:Ljav
/io/PrintStream;
57: iload_2
58: invokevirtual #28 // Method java/io/PrintStream.prin
ln:(I)V
61: iinc 3, 1
64: iload_3
65: iload 4
67: if_icmplt 49
70: return
注意:我使用的是jdk 8。
答案 2 :(得分:1)
我不会像以前的答案那样复制Java Language Specification中的粘贴,而是以可读的格式解释规范。
请考虑以下代码:
for (T x : expr) {
// do something with x
}
如果expr
求值为类似于您的情况的数组类型,则语言规范声明生成的字节码将与以下内容相同:
T[] arr = expr;
for (int i = 0; i < arr.length; i++) {
T x = arr[i];
// do something with x
}
唯一的区别是变量arr
和i
对您的代码或调试器不可见。这就是开发的原因,第二个版本可能更有用:您将返回值存储在调试器可访问的变量中。
在您的第一个版本中expr
只是函数调用,而在第二个版本中,您声明另一个变量并将函数调用的结果赋给该函数,然后将该变量用作expr
。我希望它们在性能上没有显示出可测量的差异,因为第二版中的附加变量赋值应该由JIT编译器优化,除非你也在其他地方使用它。
答案 3 :(得分:0)
foreach内部使用列表迭代器遍历列表,是的,它们之间是有区别的。
如果您只想遍历列表,而没有任何意图对其进行修改,则应该使用foreach其他方法,使用列表迭代器。
for (String i : myList) {
System.out.println(i);
list.remove(i); // Exception here
}
Iterator it=list.iterator();
while (it.hasNext()){
System.out.println(it.next());
it.remove(); // No Exception
}
如果使用foreach,您传递的列表为null,那么在java.util.ArrayList.iterator()中将获得null指针异常