是否为每次迭代创建了 for 循环内的引用变量声明? [复制]

Posted

技术标签:

【中文标题】是否为每次迭代创建了 for 循环内的引用变量声明? [复制]【英文标题】:Are reference variable declarations inside a for loop created for each iteration? [duplicate] 【发布时间】:2018-04-07 20:31:12 【问题描述】:

前 2 个例子:

1)
MyClass myClass;
for (int i=0; i<arrayList.size(); i++) 
    myClass = arrayList.get(i);
    ...



2)
for (int i=0; i<arrayList.size(); i++) 
    MyClass myClass = arrayList.get(i);
    ...

在第一个示例中,引用变量 myClass 仅创建一次。但是在第二个示例中,它是只创建一次,还是每次迭代创建一次?我的想法可能是编译器对此进行了优化,我不知道。

我试图通过编写一个示例来回答这个问题,但无法弄清楚。如何通过代码证明?

注意:我意识到示例 2 的风格更好,因为 myClass 在 for 循环之外不为人所知,并且它的范围保持在最低限度。我也在这里搜索过,但还没有找到这个确切问题的明确答案(通常是“哪个是首选?”的问题。)我还假设如果每次迭代都创建了 myClass 引用,那么它的性能并不高问题。

编辑:同样,我不是在问哪种编码风格更好。另外,我想知道它是否可以通过代码推断/证明。我尝试生成并比较字节码,但我对字节码不熟悉,生成的内容不完全匹配。

【问题讨论】:

我猜没关系,这是一个微优化,并不真正需要 我建议你检查生成的字节码,我想你会发现它们是相同的(IIRC,我没有仔细检查)。 当你声明一个变量时,你不会“创建”任何东西。变量只是一个方便的名称,可帮助您记住将某些结果放在何处,并允许您控制可以在何处使用该结果,并将某些类型信息与该结果相关联。 @AxelH 但为什么不在循环中声明呢?你不能在循环之外使用它(因为明确的分配);并且您应该始终在尽可能严格的范围内声明变量。 @MarkRotteveel 我试过这个,昨晚第一次查看字节码。它们看起来并不相同,差异很小,但我并没有真正理解这些差异,所以我想我会在这里问。 【参考方案1】:

当你声明一个变量时,你不会“创建”任何东西。变量只是一个方便的名称,可以帮助您记住将某些结果放在哪里,并允许您控制可以在何处使用该结果,并将某些类型信息与该结果相关联;但是一旦你编译了代码,它们就不再存在了。

比较字节码(*):

void outside(int i, List<?> list) 
  Object obj;
  for (i = 0; i < list.size(); i++) 
    obj = list.get(i);
  


void inside(int i, List<?> list) 
  for (i = 0; i < list.size(); i++) 
    Object obj = list.get(i);
  

反编译为:

  void outside(int, java.util.List<?>);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: aload_2
       4: invokeinterface #2,  1            // InterfaceMethod java/util/List.size:()I
       9: if_icmpge     26
      12: aload_2
      13: iload_1
      14: invokeinterface #3,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      19: astore_3
      20: iinc          1, 1
      23: goto          2
      26: return

  void inside(int, java.util.List<?>);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: aload_2
       4: invokeinterface #2,  1            // InterfaceMethod java/util/List.size:()I
       9: if_icmpge     26
      12: aload_2
      13: iload_1
      14: invokeinterface #3,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      19: astore_3
      20: iinc          1, 1
      23: goto          2
      26: return

两者是相同的。使用最易读的那个。


(*) 我已将循环变量i 声明为参数,以避免在Object 之前或之后声明i 导致生成的字节码有任何差异。这对字节码的唯一区别在于aload_N/iload_N/astore_N/istore_N 指令-N 是不同的,因为变量存储在不同的“槽”中。这不是显着差异。

【讨论】:

奇怪的是,我也试过这个,但没有办法读取字节码/对生成的文件进行适当的比较。相反,我检查了不同变体的文件大小(在循环之前定义、在循环之前和循环中定义并设置为 null),并且所有变体都有不同的文件大小(分别为 519、508 和 507 字节)跨度> @phflack javap -c. 谢谢,刚刚检查了每个变体的字节码,看起来设置 null 开始时要长 2 条指令,否则相同(如果在对象之前定义循环计数器,看起来像这就是改变我的测试的原因(519 字节的文件是空的)) @AndyTurner 这很有趣也很有帮助。谢谢你。我不太了解字节码,但我接受你的解释。我现在很好奇的是引用(以及任何相关信息,例如引用类型)是如何不存在的。 @itDontMeanAThing 我不会声称自己是专家,但这并不是很复杂。 TBH,我很少将其视为“真正的”工作;我主要用它来回答这样的问题。

以上是关于是否为每次迭代创建了 for 循环内的引用变量声明? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

我想在 for 循环中串联运行多个变量,我想取 x 和 y 的每次迭代的总和

JSLint 中迭代器变量的声明被标记为警告

在每次迭代中重新声明变量是不是比在每次迭代后重置它们更快?

在 for 循环的每次迭代中保存变量并稍后加载它们

JAVA,关于for循环,循环体里的问题

JavaScript:了解 for 循环内的 let 范围 [重复]