是否为每次迭代创建了 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 字节)跨度> @phflackjavap -c
.
谢谢,刚刚检查了每个变体的字节码,看起来设置 null 开始时要长 2 条指令,否则相同(如果在对象之前定义循环计数器,看起来像这就是改变我的测试的原因(519 字节的文件是空的))
@AndyTurner 这很有趣也很有帮助。谢谢你。我不太了解字节码,但我接受你的解释。我现在很好奇的是引用(以及任何相关信息,例如引用类型)是如何不存在的。
@itDontMeanAThing 我不会声称自己是专家,但这并不是很复杂。 TBH,我很少将其视为“真正的”工作;我主要用它来回答这样的问题。以上是关于是否为每次迭代创建了 for 循环内的引用变量声明? [复制]的主要内容,如果未能解决你的问题,请参考以下文章