针对 JDK 8 而非 7 的循环编译增强

Enhanced for loop compiling fine for JDK 8 but not 7

考虑以下代码片段,我在重构后偶然发现了为什么构建服务器报告构建损坏但在我的 IDE:

List<String> text;
...
for (String text : text) {...}

因此,for-each 中的字符串和列表使用相同的名称。

这样做当然不是很明智,但是在重命名之前跟随我的多管闲事,我看到上面的代码用 JDK 8 编译得很好,但是用 [=39= 给出了下面的错误] 7:

  error: for-each not applicable to expression type
        for (String text : text) {
                           ^
  required: array or java.lang.Iterable
  found:    String
1 error

我知道 JDK 中此区域的多个部分已进行更改 - 但有人可以告诉我为什么会出现这种行为吗?


更新: 由于我收到了一些关于不同行为的评论,这里有一个完整的示例 class:

import java.util.Arrays;
import java.util.List;

public class Strange {

    List<String> text = Arrays.asList("Max", "Alex", "Maria");

    public static void main(String[] args) {
        new Strange().doSomething("Alex");
    }

    public void doSomething(String name) {
        for (String text : text) {
            System.out.println(text.equals("Alex"));
        }
    }

}

这是编译过程和输出(Windows 7 64bit):

C:\copy>c:\Projects\java\jdk1.7.0_79\bin\javac.exe Strange.java
Strange.java:13: error: for-each not applicable to expression type
        for (String text : text) {
                           ^
  required: array or java.lang.Iterable
  found:    String
1 error

C:\copy>c:\Projects\java\jdk1.8.0_60\bin\javac.exe Strange.java

C:\copy>

结论: 我很困惑为什么我的 IDE(使用 8)没有在一个语句中抱怨两次相同的名字 - 但现在很明显它不是一个语句。我真的很想知道,如果 JLS 另有说明,为什么这一点存在这么久。但无论如何,感谢我收到的见解和很好的答案(这让我很难选择最好的答案)。

您的构建服务器可能使用与本地计算机不同的 jdk 进行编译。 (不仅仅是不同的版本号,而是完全不同的实现。)Eclipse 是使用自己的编译器的,我相信这有助于其代码热插拔。

对集合和元素使用相同的名称应该会在任何地方引起问题,但我听说过并且偶尔注意到 Eclipse 容忍 Sun/Oracle JDK不会。

我会说这是您正在使用的 Java 7 编译器的特定版本中的一个编译器错误。

前面的text是一个字段,text声明的local在for语句中shadow一个字段是合法的

那我们看看for循环是什么意思。根据 JLS,

    for (String text : text) {...}

等同于

    for (Iterator<String> #i = text.iterator(); #i.hasNext(); ) {
        String text = (String) #i.next();
        ...
    }

如您所见,内部 text 不在 text.iterator() 表达式的范围内。


我尝试搜索 Oracle Java Bugs 数据库,但找不到任何符合这种情况的内容。

这对我来说很好。我在 Netbeans 上的 64 位机器 (Windows 7) 上使用 Java 8 JDK。

我认为这是与您的 IDE 或编译器相关的本地化问题。我使用了您的确切示例,输出为

false
true
false

给出了一个警告,指出可以但不建议隐藏具有局部变量的字段。

对于 JDK 7 和 8,这实际上应该编译得很好。

引用 JLS section 14.14.2(与 Java 7 规范相同):

The enhanced for statement is equivalent to a basic for statement of the form:

for (I #i = Expression.iterator(); #i.hasNext(); ) {
      {VariableModifier} TargetType Identifier =
          (TargetType) #i.next();
      Statement
}

Iterator

重写增强的 for 循环
for (String text : text) {...}

变成

for (Iterator<String> it = text.iterator(); it.hasNext(); ) {
    String text = it.next();
}

然后,引用JLS的example 6.4.1

A similar restriction on shadowing of members by local variables was judged impractical, because the addition of a member in a superclass could cause subclasses to have to rename local variables. Related considerations make restrictions on shadowing of local variables by members of nested classes, or on shadowing of local variables by local variables declared within nested classes unattractive as well.

因此,这里没有编译时错误,因为局部变量隐藏成员变量时没有限制,这里是这样的:局部变量String text正在隐藏成员变量List<String> text.

虽然我认为其他答案都对,但我来唱反调,提出相反的观点。

显然 JDK 7 以变量 'text' 也在 ':' 之后的范围内的方式解析 foreach 循环。为了对此进行测试,我编写了以下方法。它在 Java 1.7:

中编译和运行得很好
public static void main(String[] args) {
    for (String text : new String[] {text = "hello", text, text, text})
        System.out.println(text);
}

虽然其他人说这是 jdk 1.7 中的一个错误(而且它可能是),但我在 JLS 中找不到任何地方明确指出刚刚声明的变量不在 ' 之后的范围内:'。如果不是错误,那么 Java 8 会破坏兼容性。

虽然推理,使用从增强的 for 循环到传统 for 循环的指定转换,由 使用是正确的,但有一个关于范围的明确规范:

§6.3. Scope of a Declaration

The scope of a local variable declared in the FormalParameter part of an enhanced for statement (§14.14.2) is the contained Statement.

(direct link)

因此,变量的范围不包括增强for循环的表达式

Java 7 相比,您可以验证这没有改变 Java 6, 尽管两者(我试过 Java 6 javac)都表现出矛盾的行为。

所以编译器行为的这一变化是对一个旧错误的修复…