【问题标题】:Why is declaration of the variable required inside a for-each loop in java为什么在java中的for-each循环中需要声明变量
【发布时间】:2011-09-02 10:55:45
【问题描述】:

for 每个循环的通常形式是这样的:

for(Foo bar: bars){
    bar.doThings();
}

但如果我想保留 bar 直到循环结束,我可以使用 for each 循环:

Foo bar = null;
// - Syntax error on token "bar", Identifier expected after this token
for(bar: bars){ 
    if(bar.condition())
        break;
}
bar.doThings();

for循环出现上述语法错误。

这是为什么呢? 我对解决方法不感兴趣,只是好奇这个限制背后的考虑因素。

相比之下,用普通的for循环,变量可以在外面声明也可以不声明……

int i = 1;
for(;i<max;i++){
    for(;;){
        // Do things
    }
}

【问题讨论】:

  • 您实际上可以将此行加粗,使其更加突出。 “为什么会这样?我对解决方法不感兴趣,只是好奇这个限制背后的考虑因素。”有趣的问题顺便说一句 +1。
  • 如果 bar 为空,您将在 bar.doThings() 上获得 NPE。该构造对 java 没有任何添加,除了:它允许在不接触代码的情况下交换带有 Object[](或具体 [] impl)的集合。这是我最喜欢的部分。总有编辑为您编写迭代器循环(ala for( : ))

标签: java syntax foreach


【解决方案1】:

这是一个很好的问题,我很高兴看到一些深入的答案。但是,official documentation 说:

这些缺点是众所周知的 有意识的设计师 决定去一个干净,简单 涵盖伟大的建筑 大多数情况。

绝大多数情况都是我的答案。

另一方面,我个人认为,Java 中的foreach 循环只是标准迭代器循环的一种很好的语法。因此,编译器为结构创建迭代器并使用变量来获取当前迭代的值。为确保变量已被初始化,您需要为循环范围声明它(我认为这可以防止变量在其他地方使用,例如在另一个线程中)。因此,您不能在循环之后使用该变量。但是,这只是我的意见,我很高兴听到更了解它的人的来信。 :)

编辑这是an interesting article 关于Java 中的foreach 循环。

另一个编辑我分析了(jclasslib这些方法的字节码:

 private static void testForEach(ArrayList<String> als) {
  for(String s: als)
    System.out.println(s);
 }

 private static void testIterator(ArrayList<String> als) {
  for(Iterator<String> is = als.iterator(); is.hasNext();) {
   String s = is.next();
   System.out.println(s);
  } 
 }

两种方法都用相同的字节码表示:

 0 aload_0
 1 invokevirtual #2 <java/util/ArrayList.iterator>
 4 astore_1
 5 aload_1
 6 invokeinterface #3 <java/util/Iterator.hasNext> count 1
11 ifeq 34 (+23)
14 aload_1
15 invokeinterface #4 <java/util/Iterator.next> count 1
20 checkcast #5 <java/lang/String>
23 astore_2
24 getstatic #6 <java/lang/System.out>
27 aload_2
28 invokevirtual #7 <java/io/PrintStream.println>
31 goto 5 (-26)
34 return

区别在第1行,后一种方法使用invokevirtual #8。但是,这两个调用都会调用Iterator 的相同方法。因此,正如文档所述,foreach 似乎只是编译器将其转换为预定义构造的语法糖。这并没有回答问题为什么会这样。在讨论的背景下,我认为这很有趣,可能值得了解。

【讨论】:

  • 我喜欢这个答案。大概就是这个原因。 @Waldheinz @Puce @Sorrow @Manny +1 为你们。
  • 有趣的是,我在这里提到的情况并未列为这些缺点之一。他们只提到需要直接访问迭代器的缺点......
【解决方案2】:

我猜他们只是想推广“良好实践”:来自 Java language guide

设计师们知道这些缺点,他们有意识地决定采用一个干净、简单的结构,可以涵盖绝大多数情况。

正如我们所知,您所要求的行为可以通过将 for-each 循环扩展到旧的基于迭代器的样式来模拟,并且可能他们只是认为它足够好。

【讨论】:

  • 打败我!我刚刚通读了那个文件,打算引用同一句话。
  • 有趣的是,我在这里提到的情况并未列为这些缺点之一。
  • 缺点列表不包括无法在循环外部声明其他局部变量
【解决方案3】:

在这种情况下迭代列表的方法是:

Foo bar = null;
for(Iterator<Foo> barIterator = bars.iterator(); barIterator.hasNext();){ 
   bar = barIterator.next(); // or whatever else you want to do
   ...
}

除非列表实现了 RandomAccess,否则不要使用索引遍历列表!

增强的 for 循环只是这种迭代器方法的语法糖,它将变量(此处为 bar)保持在循环的本地。 (通常,将变量保持在本地是一件好事。)

【讨论】:

  • 是的,这将是一种解决方法,但我对为什么我们不得不使用这种解决方法感兴趣。
  • 是的,抱歉,我读得太快了。实际上,我喜欢你提到的想法,在循环外声明变量名。我没有看到任何问题,它将与其他循环变体保持一致。也许您想提交增强请求:bugs.sun.com
  • 这个网站还在使用“sun.com”吗?
【解决方案4】:

这种新语法的目的是为了美化,因为它并没有真正为 Java 添加任何新内容。我的猜测是他们不允许你使用你所做的语法,唯一的原因是:它不漂亮! :)

【讨论】:

  • 啊,但有时我需要遍历几个不同的集合,并且要选择的明显变量名称对于每个集合都是相同的。使用当前的语法,我必须给它们起不同的名字。
  • 您可以使用相同的名称,因为范围在迭代之后结束.. 除非您在另一个集合中迭代一个集合,在任何情况下都需要具有不同的变量名
猜你喜欢
  • 2015-03-25
  • 2013-10-17
  • 1970-01-01
  • 2018-04-25
  • 1970-01-01
  • 2011-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多