【问题标题】:How is Java's for loop code generated by the compilerJava的for循环代码是如何被编译器生成的
【发布时间】:2010-08-08 06:08:46
【问题描述】:

编译器如何生成Java的for循环代码?

例如,如果我有:

for(String s : getStringArray() )
{
   //do something with s
}

getStringArray() 是一个函数,它返回我想要循环的数组,函数是总是被调用还是只被调用一次?一般来说,使用这种结构的循环代码有多优化?

【问题讨论】:

标签: java foreach


【解决方案1】:

论增强for循环的语义

以下是 Java 语言规范第 3 版 的相关摘录,为清晰起见稍作编辑:

JLS 14.14.2 The enhanced for statement

增强的for 语句具有以下形式:

for ( Type Identifier : Expression ) Statement

如果Expression的类型是数组类型T[],那么增强的for语句的含义由下面的基本for语句给出:

T[] a = Expression;
for (int i = 0; i < a.length; i++) {
    Type Identifier = a[i];
    Statement
}

其中ai 是编译器生成的标识符,与增强的for 语句发生时范围内的任何其他标识符(编译器生成或其他)不同。

所以事实上该语言确实保证 Expression 只会被评估一次。

为了完整起见,下面是 Expression 的类型为 Iterable 时的等价物:

JLS 14.14.2 The enhanced for statement

增强的for 语句具有以下形式:

for ( Type Identifier : Expression ) Statement

如果Expression 的类型是Iterable 的子类型,那么让I 成为表达式Expression.iterator() 的类型。增强的for 语句等效于基本的for 语句,形式如下:

for (I iter = Expression.iterator(); iter.hasNext(); ) {
    Type Identifier = iter.next();
    Statement
}

其中iter 是编译器生成的标识符,与增强的for 语句发生时范围内的任何其他标识符(编译器生成或其他)不同。

请注意,如果Expression 既不是Iterable 也不是数组,则是编译时错误,因此只有上述两种情况可以使用增强的for 循环。此外,为清楚起见,上述引用省略了有关for 循环上附加的任何标签和Identifier 上附加的任何修饰符的信息,但这些都是按预期处理的。


关于增强for循环的性能

这里引用Effective Java 2nd Edition,Item 46:Prefer for-each 循环胜过传统的 for 循环

在 1.5 版中引入的 for-each 循环通过完全隐藏迭代器或索引变量来消除混乱和出错的机会。由此产生的习语同样适用于集合和数组。 请注意,使用 for-each 循环不会降低性能,即使对于数组也是如此。事实上,在某些情况下,它可能比普通的for 循环提供轻微的性能优势,因为它只计算一次数组索引的限制。虽然您可以手动执行此操作,但程序员并不总是这样做。

因此,本书声称实际上一些编译器超越了 JLS 翻译,并对 for-each 循环执行了额外的优化(当然,同时仍保持其语义)。

总之,您不必担心 for-each 循环的性能。语言的规范是明智的(Expression 只评估一次),并且正是因为这是许多场景中的首选构造,编译器将确保尽其所能优化它们。

另见

【讨论】:

  • +1 优秀、惊人、全面的答案。如果可以的话,我会给你一个以上的支持,但遗憾的是,我不能。
【解决方案2】:

JDK 1.4 引入了RandomAccess 接口。它旨在为算法提供提示,对于给定的List 实现,迭代更有效:

for (int i = 0, n = list.size(); i < n; i++) {
    list.get(i);
}

for (Iterator i = list.iterator(); i.hasNext(); ) {
   i.next();
}

foreach 循环是否考虑到这一点?还是它完全忽略了一个给定的 Iterable 实际上是一个 List 的事实?

应该注意的是,这意味着添加一个(iterable instanceof List &amp;&amp; iterable instanceof RandomAccess) 测试和一个向下转换到List 会起作用,这会增加一个并不总是值得的开销,并且可能被认为是编译器的过早优化语法糖功能。

【讨论】:

  • RandomAcces 并不意味着基于索引的循环更有效。这仅意味着使用该索引不会明显更糟。与相反的LinkedList 进行比较。对于这种实现,使用索引循环将是灾难性的。因此,其他类的标记接口指示何时不是这种情况。但是基于迭代器的循环对于所有集合来说已经足够了,可以一直使用。顺便说一句,新问题不应该作为答案发布。
【解决方案3】:

编译器可能只调用一次,但您可以依赖它。这可能不是一个好的编码习惯。 如果getStringArray() 每次都返回相同的数组,为什么不先设置一个变量呢?

EDIT - 收到的 cmets 改变了答案。

【讨论】:

  • 可以事实上依赖于它被调用一次。见java.sun.com/docs/books/jls/third_edition/html/…
  • 我认为在合理的范围内调用Expression 中的方法是可以的。 for (char c : str.toCharArray())for (MyEnum e : MyEnum.values()) 等似乎是惯用的。
  • 为什么不设置一个永远不会改变的变量呢?因为所做的只是复制。
【解决方案4】:

for循环和javascript中的循环一样,所以不用担心

示例:

for(int i=0;i<10;i++)
{
    System.out.Println(i);
}

【讨论】:

    猜你喜欢
    • 2016-01-20
    • 1970-01-01
    • 1970-01-01
    • 2020-01-12
    • 1970-01-01
    • 2014-05-29
    • 1970-01-01
    • 1970-01-01
    • 2016-08-12
    相关资源
    最近更新 更多