【问题标题】:Enhanced for loop compiling fine for JDK 8 but not 7增强的 for 循环编译适用于 JDK 8 但不是 7
【发布时间】:2016-01-31 01:08:22
【问题描述】:

考虑以下代码 sn-p,在检查构建服务器报告构建损坏但在我的 IDE 中很好的原因时,我在进行了一些重构后偶然发现:

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

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

这样做当然不是很明智,但是在重命名之前遵循我的多管闲事后,我看到上面的代码在 JDK 8 上编译得很好,但在 JDK 7 下出现以下错误:

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

我知道在 JDK 中对这方面的几个部分进行了更改 - 但有人能告诉我为什么会发生这种行为吗?


更新: 因为我得到了一些关于不同行为的 cmets,所以这里有一个完整的示例类:

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 另有规定,我真的很想知道为什么这一点已经存在了这么久。但无论如何,感谢我收到的见解和出色的答案(这让我很难选择最好的答案)。

【问题讨论】:

  • 他不是在寻求解决方案,而是在寻求解释。这是一个有趣的行为,我也想知道
  • 我怀疑 text 和 text 隐藏了同一个字段。 Java 8 可能对此更加放松,并意识到这是循环中的两个不同变量,但 Java 7 没有。您是否尝试重命名变量以使它们具有不同的名称?
  • @TheLaw:反之亦然,7 失败。当然,当使用不同的名称时,两者都有效。
  • @Mel:感谢您指出这一点 - 当然我通常不会做类似命名两者相同的事情 - 它只是偶然发生的,我只是在构建损坏后才意识到它并开始想知道.
  • 我尝试使用 JDK 8 (1.8.0_31) 进行编译,但出现错误“变量文本已在方法中定义”

标签: java foreach java-8 java-7 language-lawyer


【解决方案1】:

对于 JDK 7 和 8,这实际上应该可以正常编译。

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

增强的 for 语句相当于基本的 for 语句,形式如下:

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

对局部变量对成员的影子的类似限制被认为是不切实际的,因为在超类中添加成员可能会导致子类必须重命名局部变量。相关考虑使得限制嵌套类的成员对局部变量的遮蔽,或者对嵌套类中声明的局部变量对局部变量的遮蔽也没有吸引力。

因此,这里没有编译时错误,因为在通过局部变量遮蔽成员变量时没有任何限制,这里就是这种情况:局部变量String text 遮蔽了成员变量List&lt;String&gt; text

【讨论】:

    【解决方案2】:

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

    §6.3. Scope of a Declaration

    FormalParameter部分声明的局部变量的作用域 增强的 for 语句 (§14.14.2) 是包含的语句

    (direct link)

    因此,变量的作用域不包括增强的for循环的表达式……

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

    所以编译器行为的这种变化是对旧错误的修复......

    【讨论】:

      【解决方案3】:

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

      前面的text是一个字段,在for语句中声明的text局部覆盖字段是合法的。

      然后我们看看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 Database,但找不到与此场景匹配的任何内容。

      【讨论】:

        【解决方案4】:

        虽然我认为其他答案是正确的,但让我成为魔鬼的拥护者并提供相反的观点。

        显然 JDK 7 以这样一种方式解析 foreach 循环,即变量 'text' 也在 ':' 之后的范围内。为了测试这一点,我编写了以下方法。它在 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 会破坏兼容性。

        【讨论】:

          【解决方案5】:

          您的构建服务器可能使用与本地计算机不同的 jdk 进行编译。 (不只是版本号不同,而是完全不同的实现。)Eclipse是一个使用自己的编译器的,我相信它可以方便它的代码热交换。

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

          【讨论】:

          • 不,这只会让我找到问题的根源。我的机器上有最新的 2 个 JDK,分别是 7 和 8,每个都编译相同的代码,在 7 中得到编译错误,在 8 中没有。它与 IDE 或 OS 无关。
          • 如果你确信你在两个测试中使用相同的 JDK 供应商,那么我倾向于将它报告为一个错误(在 1.8 中),尽管我认为它可能有某种相关性到新的 lambda 表达式支持。
          • @BradMace 这实际上是 7 中的一个错误。这应该可以工作。
          • 我很不情愿地相信 Java 有必要根据其他一些答案来允许这样做。虽然这是一种糟糕的代码编写方式,但应尽可能避免。
          【解决方案6】:

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

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

          false
          true
          false
          

          给出了一条警告,指出可以,但不建议使用局部变量隐藏字段。

          【讨论】:

          • 那么 java 7 呢?它在那里发生编译错误,8对我来说也很好。标题有错字,但问题解释正确。
          • 我刚刚在Java 7上测试过,输出没有区别:compile-single: run-single: false true false
          猜你喜欢
          • 1970-01-01
          • 2016-03-23
          • 2018-03-13
          • 1970-01-01
          • 1970-01-01
          • 2014-02-23
          • 2023-03-09
          • 2011-01-20
          相关资源
          最近更新 更多