4

我知道已经提出并回答了非常相似的问题,我阅读了我能够找到但仍然不是 100% 清楚的问题。

考虑到这个代码片段:

public static void fooMethod {

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

没有内部类,没有其他特别或不寻常的东西。对我来说似乎违反直觉。

在上述示例中声明局部变量是否有final任何用途?

我是否正确理解有或没有final这里的编译器会产生完全相同的字节码?

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

后续问题(如果可以)

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

public static void fooMethod2 {

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

3 回答 3

9

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

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

下面的测试证实了这一信息。



1:如果编译器可以做一些事情,使用final会有所不同:

看看这个片段:

boolean zZ = true;
while (zZ) {
    int xX = 1001;         // <------------- xX
    int yY = 1002;         // <------------- yY
    zZ = (xX == yY);
}

两个int变量,xXyY。第一次宣布两者为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)。

由此,我们可以得出结论,在字节码方面,编译器确实可以在使用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相等,对吧?

因为如此,我们可以看到:当我们生成有或没有final.

虽然,在一般情况下有人说其他人不同意在本地块final中使用,有性能优势,这绝对只是一种风格选择。final


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

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

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

...以及为该片段生成的字节码...

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

...完全相同(当然,只有行号发生了变化)。

使用对象(不仅是原始类型变量)的其他测试显示了相同的行为。

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

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

于 2013-05-26T02:14:33.810 回答
4

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

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

例如:

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

    temp = 5; // compiler error
}

但如果它不是恒定的(最终的):

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

    temp = 5; // fine
}
于 2013-05-26T01:59:41.933 回答
1

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

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

我真的很感谢“finals”,因为如果您稍后阅读您的代码,您第一眼就会看到这个分配永远不会改变一些行。如果您知道实例是不可变的,这也会有所帮助。

此外,如果您始终如一地使用“final”,非final 将获得可见性,而这些变量通常是最重要的要观察的变量。

于 2013-05-26T07:24:17.137 回答