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

标签 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>;
     ....
   }
}

最佳答案

简而言之: 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时,编译器知道它们不相等并且从不比较它们(生成falsexX == 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 是否相等,对吗?

正因为如此,我们可以看到:生成的字节码完全相同(相同的MD5!)当我们生成类有或没有final.

同时,在一般情况下some sayothers disagree使用 final 有性能优势,在本地 block 中,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 下进行。

关于java - 在循环中将局部变量声明为 final,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16755554/

相关文章:

java - Android 中的 ArrayList 最终

java - Byte Buddy 可以访问方法的局部变量名吗?

java - drools 6.0 中未知的 KieSession 名称(尝试将 drools 添加到现有的 maven/eclipse 项目中)

java - 使用 regExp 返回子字符串 [Java]

java - 查找java数组中元素的频率

javascript - 在 C# Web 浏览器中使 javascript 变量全局作用域

Android - Activity 的局部变量与实例变量的性能

java - StandardEvaluationContext 中的 Spring StandardTypeLocator : registering new import prefixes for use in Thymeleaf templates

constructor - 如何在 Kotlin 的构建过程中修改 val 成员

java - 是否可以通过反射来确定方法参数是否是最终的?