for-each循环如何在JAVA内部工作?

当我进行函数调用时,我试图找到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调试代码时,我看到方法调用只发生一次!

任何人都可以解释它是如何工作的 ? 当我编写复杂对象时, 哪个更有效/更好

我不打算从Java语言规范中复制粘贴,就像之前的答案之一那样,而是以可读格式解释规范。

请考虑以下代码:

 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 } 

不同之处仅在于,变量arri对您的代码或调试器不可见。 这就是开发的原因,第二个版本可能更有用:您将返回值存储在调试器可访问的变量中。

在您的第一个版本中, expr只是函数调用,而在第二个版本中,您声明另一个变量并将函数调用的结果赋给该函数,然后将该变量用作expr 。 我希望它们在性能上没有显示出可测量的差异,因为第二个版本中的额外变量赋值应该由JIT编译器优化,除非你也在其他地方使用它。

Java语言规范显示了基础编译

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语句中使用。

编译器只调用 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。

foreach内部使用list迭代器来遍历列表,是的,它们之间存在差异。

如果您只想遍历列表并且没有任何意图来修改它,那么您应该使用foreach else使用list iterator。

 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()中获得空指针exception