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

时间:2015-02-09 07:08:40

标签: 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调试代码时,我看到方法调用只发生一次!

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

4 个答案:

答案 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
}

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

在您的第一个版本中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指针异常