【问题标题】:Foreach vs common for loopForeach 与常见的 for 循环
【发布时间】:2013-09-13 14:02:37
【问题描述】:

我刚开始学习 Java,我遇到的第一件事是 foreach 循环,我做的第一件事是不知道它是如何工作的:

int[] array = new int [10];
for (int i: array){
    i = 1;
}

显然未能将1 分配给数组的每个元素。然后我在循环体中添加了System.out.print(i);(在i = 1; 之后),并看到屏幕的输出是1111111111,但是由于在循环内使用i 做某事是有效的,所以i 很可能是数组的每个元素的副本,不是吗? (第一个问题)

如果上述情况属实,这是否意味着 foreach 循环比普通 for 循环慢得多,因为它涉及复制数组的每个元素?或者由于Java 没有指针和指针算法,oprator[] 可能以其他“糟糕”的方式设计,复制每个元素实际上更快?

如果上述假设成立,为什么要使用明显较慢的foreach 循环而不是普通的for循环?

简而言之,问题:

  • iarray 的每个元素的副本吗?如果不是那是什么 那么呢?

  • foreach 循环不是比普通循环慢吗?如果没有,如何 那么“糟糕”是operator[] 设计的吗?

  • 除了可读性之外,在foreach 循环中获胜没有别的了吗?

【问题讨论】:

    标签: java foreach


    【解决方案1】:

    在代码中

    for (int i: array){
    

    您声明了一个变量i,它在每次循环迭代时获取数组中下一个元素的值,它不是对该元素的引用。

    i = 1;
    

    您将新值分配给变量,而不是数组中的元素。

    您不能直接使用 foreach 循环设置数组元素的值。为此使用普通的for 循环

    for (int i = 0; i < array.length; i++) {
        array[i] = ...; // some value
    }
    

    在上面的示例中,您使用声明的变量i 作为数组中元素的索引。

    array[i] 
    

    正在访问您可以修改其值的元素本身。


    Ans 显然未能将 1 分配给数组的每个元素。我 添加 System.out.print(i);到循环的主体,看到 屏幕的输出是 1111111111 但因为我在做某事 循环内部是有效的,很可能 i 是每个元素的副本 数组,不是吗? (第一个问题)

    你必须把System.out.print(i)放在i = 1之后,否则你会得到0000000

    如果上述情况属实,这是否意味着 foreach 循环很多 比常见的 for 循环慢,因为它涉及复制 数组的每个元素?或者因为 Java 没有指针和 指针算法,操作符 [] 可以设计成其他的 复制每个元素实际上更快的“糟糕”时尚?

    查看here 以了解 foreach 循环的工作原理。对于数组,

    for (int i: array){
        i = 1;
    }
    

    等价于

    for (int index = 0; index < array.length; index++) {
        int i = array[index];
        i = 1;
    }
    

    所以它并不慢。您正在堆栈上进行另一项原始创建。

    这取决于实现。对于数组,它不会以任何方式变慢。它只是用于不同的目的。

    为什么要使用明显较慢的 foreach 循环而不是普通循环 循环?

    一个原因是可读性。另一个是当您不关心更改数组的元素引用,而是使用当前引用时。

    以引用类型为例

    public class Foo {
        public int a;
    }
    
    Foo[] array = new Foo[3];
    for (int i = 0; i < array.length; i++) {
        array[i] = new Foo();
        array[i].a = i * 17;
    }
    
    for (Foo foo : array) {
        foo.a = 0; // sets the value of `a` in each Foo object
        foo = new Foo(); // create new Foo object, but doesn't replace the one in the array
    }
    

    对于原始类型,这样的事情是行不通的。

    for (int index = 0; index < array.length; index++) { 
        int i = array[index];
        i = 1; // doesn't change array[index]
    }
    

    【讨论】:

    • 我知道。这不是我的问题。
    • @AlexandruBarbarosie 看看实际答案。
    • @AlexandruBarbarosie 我已经突出显示了我的答案。
    • 所以i 是数组中每个元素的副本?查看您提供链接的问答并假设在每个foreach 循环的背景中调用i.next();,它确实使它变慢。您还说“它并没有以任何方式变慢”,那么:它更快吗?那么 [] 设计得有多糟糕,使用 [] 运算符制作副本更快访问元素?除了可读性之外,没有什么能在 foreach 循环中获胜了?
    • @AlexandruBarbarosie i 保存数组中每个元素的值的副本。更改 i 不会更改元素。我已经用等效的转换更新了我的答案。如果您只想使用数组的值而不是更改它们,请使用 foreach 循环。
    【解决方案2】:

    来自 Joshua Bloch 在 Effective Java 中的第 46 条:

    1.5 版中引入的 for-each 循环消除了混乱 以及隐藏迭代器或索引变量的错误机会 完全地。由此产生的习语同样适用于集合和 数组:

    // The preferred idiom for iterating over collections and arrays
    for (Element e : elements) {
        doSomething(e);
    }
    

    当您看到冒号 (:) 时,将其读作“in”。因此,上面的循环 读作“对于元素中的每个元素 e”。请注意,没有 使用 for-each 循环的性能损失,即使对于数组也是如此。在 事实上,它可能比普通的 在某些情况下循环,因为它计算数组的限制 只索引一次。虽然你可以手动完成(第 45 项),但程序员 不要总是这样做。

    优势

    1. 可读性
    2. 它增加了抽象级别 - 而不必表达如何循环列表的低级细节 或数组(带有索引或迭代器),开发人员只需声明 他们想要循环,剩下的事情由语言来处理。

    缺点:

    无法访问索引或删除项目。

    总结一下,

    增强的 for 循环提供

    1. 一种简洁的高级语法,用于遍历列表或数组

      1.1 提高清晰度

      1.2 可读性。

      但是,它错过了:允许访问索引循环或删除项目。

    【讨论】:

      【解决方案3】:

      通过引用设置值不起作用,因为int 是原始类型。对于任何类型的 Object[] 都可以。制作原始类型的副本非常快,并且会在您不知道的情况下由处理器完成很多次。

      foreach 循环更易读,但也更易写。一个常见的程序员错误是:

      for (int i = 0; i < 10; i++)
      {
          for(int j = 0; j < 10; i++) //oops, incrementing wrong variable!
          {
              //this will not execute as expected
          }
      }
      

      使用foreach 循环是不可能出现此错误的。

      【讨论】:

      • 它也不适用于引用类型。您将只是更改变量的引用。
      • 好点,取决于你在做什么。如果是AtomicInteger[],而你在他们身上打电话给set,他们就会改变。
      • 这就是区别:调用方法或访问字段并实际重新分配引用/变量。
      【解决方案4】:

      for 语句还有另一种形式,专为通过集合和数组进行迭代而设计。这种形式有时被称为增强的 for 语句,可用于使您的循环更加紧凑和易于阅读。 http://docs.oracle.com/javase/tutorial/java/nutsandbolts/for.html

      所以,你是对的。可读性是这里的主要胜利。有关 for each 循环或增强型 for 循环的更多信息,请访问blog entry from oralce

      for each 使用了Iterable&lt;E&gt; 接口的功能,因此性能取决于实现。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-08-10
        • 1970-01-01
        • 2016-11-28
        • 2013-05-25
        • 1970-01-01
        • 2017-07-14
        • 2018-01-14
        • 2014-04-19
        相关资源
        最近更新 更多