在循环中将局部变量声明为final

时间:2013-05-26 01:57:16

标签: java final local-variables

我知道已经提出并回答了非常相似的问题,我读了我能找到的那些并且仍然不是100%明确的。

考虑此代码段:

public static void fooMethod {

   while(<...>) {
     ....
     final int temp = <something>;
     ....
   }
}

没有内在的课程,没有其他特殊的或不寻常的。对我来说似乎是违反直觉的。

在上面的示例中声明局部变量final是否可以用于任何目的?

我是否正确理解无论有没有final,编译器都会产生完全相同的字节码?

我在这里遗漏了什么吗?如果是RTFM案例,请指出正确的方向。

后续问题(如果可以的话)

通过这样的重写来获得和/或失去什么(理解temp不必是原始的?)

public static void fooMethod2 {

   int temp;
   while(<...>) {
     ....
     temp = <something>;
     ....
   }
}

3 个答案:

答案 0 :(得分:9)

简而言之: final关键字,用于局部变量和参数时,不会生成字节码({{1并且,正如预期的那样,它的使用在运行时没有任何影响。 (编译时,它可能会有所不同,但请查看以下内容。)

在这些情况下,当由于匿名内部类而未强制执行时,它只是样式选项,在记录变量的预期范围时非常有用。

以下测试确认了这些信息。



1:如果编译器可以创建它,使用.class会产生差异:

请看这个片段:

final

两个boolean zZ = true; while (zZ) { int xX = 1001; // <------------- xX int yY = 1002; // <------------- yY zZ = (xX == yY); } 个变量,intxX。第一次宣布为yY和第二次,从两者中取走final。以下是生成的字节码(使用final打印):

javap -c

final

0: iconst_1 // pushes int 1 (true) onto the stack 1: istore_1 // stores the int on top of the stack into var zZ 2: goto 15 5: sipush 1001 // pushes 1001 onto the operand stack 8: istore_2 // stores on xX 9: sipush 1002 // pushes 1002 onto the operand stack 12: istore_3 // stores on yY 13: iconst_0 // pushes 0 (false): does not compare!! <--------- 14: istore_1 // stores on zZ 15: iload_1 // loads zZ 16: ifne 5 // goes to 5 if top int (zZ) is not 0 19: return

final

在上面的例子中,当它们是 // 0: to 12: all the same 13: iload_2 // pushes xX onto the stack 14: iload_3 // pushes yY onto the stack 15: if_icmpne 22 // here it compares xX and yY! <------------ 18: iconst_1 19: goto 23 22: iconst_0 23: istore_1 24: iload_1 25: ifne 5 28: return 时,编译器知道它们不相等并且从不比较它们final在字节码中生成false xX == yY 1}}是)。

从这一点来看,我们可以得出结论,在字节码方面,编译器可以在使用final时对生成的代码进行一些优化。 (我不是说它们有意义,但肯定final不仅仅是样式选择。)


2:如果编译器无法做出任何结论,在本地变量上使用final只是一个设计选择:

现在请使用以下代码:

boolean zZ = true;
int aA = 1001;
int bB = 1002;
while (zZ) {
    final int xX = aA;   // <------- took away the "final" here, didnt matter
    final int yY = bB;   // <------- took away the "final" here, didnt matter
    zZ = (xX == yY);
}

在这种情况下,即使使用final,如果xXyY相等,编译器也无法告诉编译时,对吗?

因此,我们可以看到:生成的字节码完全相同(相同的MD5!)当我们使用或不使用final 生成类

虽然在一般情况some sayothers disagree中,在本地区块中使用final会带来性能优势,{{ 1}}绝对只是 style 的选择。


3:循环内部或外部的局部变量 - 完全没有区别:

此代码段的生成字节码...

final

...以及此代码段的生成字节码...

boolean zZ = true;
int aA = 1001, bB = 1002;
while (zZ) {
    int xX = aA;                      // <--- declaration is inside WHILE
    int yY = bB;
    zZ = (xX == yY);
}

... 完全相同(当然只改变了行号)。

使用对象的其他测试(不仅是原始类型变量)也表现出相同的行为。

可以安全地得出结论,如果没有在其他地方使用,在循环内部或外部声明局部变量几乎是 设计选择 ,没有字节码效果。

注意:所有测试均在Oracle的JRE版本1.7.0_13下进行。

答案 1 :(得分:4)

final是常量变量的关键字。将其声明为最终会阻止您稍后在循环内重新分配它。

temp将在每次迭代时重新声明,无论它是否是最终的。

例如:

while (...)
{
    final int temp = ...;

    temp = 5; // compiler error
}

但如果它不是常数(最终):

while (...)
{
    int temp = ...;

    temp = 5; // fine
}

答案 2 :(得分:1)

从完全不同的角度考虑这一点:在功能编程语言中,几乎所有赋值都是最终的,并且类是不可变的是正常情况。这意味着非最终作业和/或可变类是例外

如果您的代码是用Scala编写的,IntelliJ IDE会显示一条提示“此分配可以更改为最终”。

我非常感谢“决赛”,因为如果您稍后阅读了您的代码,您会在第一眼看到此分配从未进一步改变某些行。如果你知道实例是不可变的,这也会有所帮助。

此外,如果你一直使用“韵母”,那么非最终版本将获得可见性,而这些变量通常是最重要的观察点。