【问题标题】:Is there any difference between these two loops?这两个循环有什么区别吗?
【发布时间】:2014-05-04 04:14:47
【问题描述】:

下面的两个代码 sn-ps 在性能方面有什么不同吗?

for(String project : auth.getProjects()) {
    // Do something with 'project'
}

String[] projects = auth.getProjects();
for(String project : projects) {
    // Do something with 'project'
}

对我来说,我认为第二个更好,但更长。第一个较短,但我不确定它是否更快。我不确定,但对我来说,似乎每次迭代该循环时,都会调用 auth.getProjects。不是这样吗?

【问题讨论】:

  • 它只被调用一次,但是第二个版本太冗长了。你所做的只是通过一个列表。一行就够了。

标签: java performance loops


【解决方案1】:

编辑@StephenC 是对的,JLS 是寻找此类问题答案的更好地方。这是语言规范中的link to the enhanced for loop。在那里你会发现它生成了几种不同类型的 for 语句,但它们都不会调用该方法超过 1 次。


简单测试表明该方法只被调用一次

public class TestA {
    public String [] theStrings;

    public TestA() {
        theStrings = new String[] {"one","two", "three"};
        for(String string : getTheStrings()) {
            System.out.println(string);
        }
    }

    public String[] getTheStrings() {
        System.out.println("get the strings");
        return theStrings;
    }

    public static void main(String [] args) {
        new TestA();
    }
}

输出:

get the strings
one
two
three

所以本质上它们是一样的。唯一可能对第二个有益的事情是,如果您想在 for 循环之外使用数组。


编辑

你让我好奇 java 编译器是如何处理这个问题的,所以我使用上面的代码反编译了类文件,结果是什么

public class TestA
{

    public TestA()
    {
        String as[];
        int j = (as = getTheStrings()).length;
        for(int i = 0; i < j; i++)
        {
            String string = as[i];
            System.out.println(string);
        }

    }

    public String[] getTheStrings()
    {
        System.out.println("get the strings");
        return theStrings;
    }

    public static void main(String args[])
    {
        new TestA();
    }

    public String theStrings[] = {
        "one", "two", "three"
    };
}

如您所见,编译器只是将您的 for 循环重组为标准循环!也进一步证明了编译器通过后其实它们是一模一样的。

【讨论】:

  • 谢谢。现在我清楚地知道幕后到底发生了什么。
  • “简单的测试表明该方法只被调用一次” - 简单的测试可能会产生误导......'因为你不知道你应该测试什么。更好的方法是阅读 JLS。
【解决方案2】:

假设in 你的意思是:,在性能上没有区别;他们都做同样的事情。即使在第一个示例中, auth.getProjects() 也只执行一次;它不能多次执行,因为如果是,for 迭代每次都必须重新开始,这不是它的工作原理。

我建议使用您认为更清晰的版本。

【讨论】:

    【解决方案3】:

    第二个示例中还有一些操作。您不是直接使用该方法引用数组,而是声明一个新的引用变量,将其存储在该新变量中,然后引用该新变量。

    您可以使用 ASM Bytecode Outline 查看字节码。

    但是,这是微优化。如果您需要一个新的参考变量,然后创建一个。如果不需要,请保存工作,不要创建新的。

    【讨论】:

    • 您的意思是“第二个示例”而不是“第一个示例”吗?第二个例子是声明一个新的引用变量的例子。虽然,如果引用变量没有在函数的其他地方使用,大多数编译器可能会在这两种情况下使用寄存器。
    • @WarrenDew 是的,我编辑了我的答案,感谢您指出这一点
    【解决方案4】:

    没有区别。

    根据JLS 14.14.2,增强的for 循环(对于数组类型)等效于具有以下模式的常规for 循环:

    T[] #a = Expression;
    L1: L2: ... Lm:
    for (int #i = 0; #i < #a.length; #i++) {
        {VariableModifier} TargetType Identifier = #a[#i];
        Statement
    }
    

    如果我们替换您的第一个示例:

    for(String project : auth.getProjects()) {
       // Do something with 'project'
    }
    

    我们得到:

    String[] $a = auth.getProjects();
    for (int $i = 0; $i < $a.length; $i++) {
        String project = $a[$i];
        // Do something with 'project'
    }
    

    第二个例子:

    String[] projects = auth.getProjects();
    for(String project : projects) {
        // Do something with 'project'
    }
    

    我们得到:

    String[] projects = auth.getProjects();
    String[] $a = projects;
    for (int $i = 0; $i < $a.length; $i++) {
        String project = $a[$i];
        // Do something with 'project'
    }
    

    这两个版本的代码显然是等价的,假设javac 或JIT 能够优化掉冗余的局部变量$a,这两个版本应该执行相同的操作。

    请注意,在这两种情况下,局部变量 $a 仅在循环开始时创建一次。

    【讨论】:

      【解决方案5】:

      第二个在性能方面更好,它不会多次创建变量。因此,是的,每次都会调用 auth.getProjects。

      【讨论】:

      • 不正确。保存auth.getProjects() 的局部变量只创建和分配一次。查看我的答案。
      • 对不起,我晚上写的xD
      【解决方案6】:

      两者都与上面找到的相同。

      但是做一件事使用第一个方法并在 for 循环之后,取消引用项目变量为- 项目=空; 否则,它将在整个方法生命周期内保持活动状态,并且会消耗不必要的内存。

      【讨论】:

      • 不正确。 getProjects 方法不是“在循环中”调用的。它在循环开始时被调用一次。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-10-14
      • 2017-06-10
      • 1970-01-01
      • 2019-11-15
      • 2013-11-14
      • 2020-02-02
      • 2023-01-26
      相关资源
      最近更新 更多