带有最终局部变量的 Java 编译器优化

jul*_*2K2 4 java compiler-construction variables methods final

我一直认为 final 关键字在性能方面对本地方法变量或参数没有影响。因此,我尝试测试以下代码,但似乎我错了:

private static String doStuffFinal() {
    final String a = "A";
    final String b = "B";
    final int n = 2;
    return a + b + n;
}

private static String doStuffNotFinal() {
    String a = "A";
    String b = "B";
    int n = 2;
    return a + b + n;
}
Run Code Online (Sandbox Code Playgroud)

我检查了字节码,这两种方法不一样。idea中的反编译代码如下所示:

private static String doStuffFinal() {
    String a = "A";
    String b = "B";
    int n = 2;
    return "AB2";
}

private static String doStuffNotFinal() {
    String a = "A";
    String b = "B";
    int n = 2;
    return a + b + n;
}
Run Code Online (Sandbox Code Playgroud)

为什么这两种方法有区别?javac就不能优化这样一个微不足道的案例吗?编译器可以看到 a、b 和 n 在 doStuffNotFinal 中没有变化,并以相同的方式优化代码。为什么不会发生这种情况?

更重要的是,这是否意味着我们最好将 final 关键字放在所有地方,以确保获得最佳优化?

And*_*ner 7

为什么这两种方法有区别?

abn恒定的变量doStuffFinal()方法,因为:

常量变量是用常量表达式初始化的原始类型或字符串类型的最终变量(第 15.29 节)

但是 indoStuffNotFinal中的变量不是常量变量,因为它们不是最终的,因此它们的值不是常量表达式。

常量表达式中所述,具有常量表达式操作数的二元运算符的结果也是常量表达式;soa + b是一个常量表达式, 也是a + b + n。并且:

String 类型的常量表达式始终是“实习的”

因此,a + b + n是实习的,所以会出现在常量池中,这样你在反编译时就会看到它的用途。


javac就不能优化这样一个微不足道的案例吗?

语言规范说常量字符串必须在最后一种情况下被实习;它并没有说它不能在非最终情况下。所以,当然可以。

一天只有这么多时间;编译器实现者只能做这么多。在编译器中优化这种微不足道的情况可能是无趣的,因为它非常罕见。


这是否意味着我们最好把 final 关键字放在所有地方

不要忘记 javac 并不是唯一进行优化的东西:javac 实际上非常愚蠢,并且在将 Java 代码转换为字节码时是字面意义的。更有趣的优化出现在 JIT 中。

此外,您只能在非常特定的情况下获得使事情最终化的好处:使用常量表达式初始化的最终字符串或原始变量。这当然取决于您的代码库,但这些不会占您变量的很大一部分。

因此,当然您可以将它们喷洒在任何地方,但它带来的好处不太可能超过final散布在您的代码中的额外视觉噪音。