Java 使用最终局部变量的编译器优化

Java compiler optimizations with final local variables

我一直认为 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;
}

我检查了字节码,这两种方法并不相同。 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;
}

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

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

Why is there a difference between these 2 methods?

abndoStuffFinal()方法中是constant variables,因为:

A constant variable is a final variable of primitive type or type String that is initialized with a constant expression (§15.29)

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

Constant expressions所述,常量表达式操作数的二元运算符的结果也是常量表达式;所以a + b是常量表达式,a + b + n也是。还有:

Constant expressions of type String are always "interned"

所以a + b + n是interned的,所以会出现在常量池中,反编译的时候会看到它的用处。


Can't javac optimize such a trivial case?

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

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


does that mean we'd better put the final keyword all over the place

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

此外,只有在非常特殊的情况下才能获得将事物设为 final 的好处:final String 或使用常量表达式初始化的原始变量。这当然取决于您的代码库,但这些不会占您变量的很大一部分。

因此,您当然可以 将它们喷洒到各处,但它提供的好处不可能超过让 final 分散在代码中的额外视觉噪音。