在Java中,我们看到很多地方可以使用final关键字,但它的使用并不常见。

例如:

String str = "abc";
System.out.println(str);

在上面的例子中,str可以是final,但通常不使用。

当一个方法永远不会被覆盖时,我们可以使用final关键字。类似地,类不会被继承。

在任何或所有这些情况下使用final关键字真的能提高性能吗?如果是,那该怎么做?请解释一下。如果final的正确使用确实关系到性能,那么Java程序员应该养成什么样的习惯才能最好地利用这个关键字呢?


当前回答

我不是专家,但我认为你应该在类或方法中添加final关键字,如果它不会被覆盖并留下变量。如果有任何方法来优化这样的东西,编译器会为你做。

其他回答

Final(至少对于成员变量和参数)更适合人类,而不是机器。

在任何可能的情况下使变量为final是一个很好的实践。我希望Java默认设置“变量”为final,并有一个“可变”关键字来允许更改。不可变类会带来更好的线程代码,只要看一眼每个成员前面都有“final”的类,就会很快显示它是不可变的。

另一种情况——我已经转换了很多代码来使用@NonNull/@Nullable注释(你可以说一个方法参数必须不是null,然后IDE可以警告你每一个地方你传递一个没有@NonNull标记的变量——整个事情蔓延到一个荒谬的程度)。当一个成员变量或形参被标记为final时,证明它不能为空要容易得多,因为你知道它没有在其他任何地方被重新赋值。

我的建议是养成在默认情况下为成员和参数应用final的习惯,它只是几个字符,但如果没有其他的话,将推动您改进您的编码风格。

方法或类的Final是另一个概念,因为它不允许非常有效的重用形式,并且没有真正告诉读者很多东西。最好的使用可能是他们使String和其他内在类型为final的方式,这样你就可以在任何地方依赖一致的行为——这防止了很多错误(尽管有时我很喜欢扩展String ....)哦,可能性)

我不是专家,但我认为你应该在类或方法中添加final关键字,如果它不会被覆盖并留下变量。如果有任何方法来优化这样的东西,编译器会为你做。

你实际上在问两种(至少)不同的情况:

局部变量的Final 方法/类的Final

Jon Skeet已经回答了2)。关于1):

我不认为这有什么区别;对于局部变量,编译器可以推断该变量是否是final变量(只需检查它是否被多次赋值)。因此,如果编译器想要优化只赋值一次的变量,无论变量是否声明为final,它都可以这样做。

Final可能会对protected/public类字段产生影响;在那里,编译器很难发现字段是否被设置了不止一次,因为它可能发生在不同的类中(甚至可能没有被加载)。但即使这样,JVM也可以使用Jon描述的技术(乐观地优化,如果加载了更改字段的类,则恢复)。

总之,我看不出有任何理由它应该有助于性能。 所以这种微观优化不太可能有帮助。您可以尝试对其进行基准测试以确定,但我怀疑它是否会产生影响。

编辑:

实际上,根据Timo Westkämper的回答,final在某些情况下可以提高类字段的性能。我接受纠正。

令我惊讶的是,居然没有人发布一些经过反编译的真实代码,以证明至少有一些小的差异。

作为参考,已经在javac版本8,9和10上进行了测试。

假设这个方法:

public static int test() {
    /* final */ Object left = new Object();
    Object right = new Object();

    return left.hashCode() + right.hashCode();
}

按原样编译这段代码,生成的字节代码与出现final时完全相同(final Object left = new Object();)。

但是这个:

public static int test() {
    /* final */ int left = 11;
    int right = 12;
    return left + right;
}

生产:

   0: bipush        11
   2: istore_0
   3: bipush        12
   5: istore_1
   6: iload_0
   7: iload_1
   8: iadd
   9: ireturn

把final留给present会产生:

   0: bipush        12
   2: istore_1
   3: bipush        11
   5: iload_1
   6: iadd
   7: ireturn

代码几乎是不言自明的,如果有编译时间常数,它将被直接加载到操作数堆栈中(它不会像前面的例子那样通过bipush 12存储到局部变量数组中;istore_0;Iload_0)——这是有意义的,因为没有人可以改变它。

另一方面,为什么在第二种情况下编译器没有生成istore_0…iload_0超出了我,它不像插槽0被以任何方式使用(它可以缩小变量数组这种方式,但可能是我错过了一些内部细节,不能确定)

我很惊讶看到这样的优化,因为javac做的事情很少。至于我们应该总是使用final吗?我甚至不打算写一个JMH测试(这是我最初想做的),我确信差异是按ns的顺序(如果可能的话)。唯一可能出现问题的地方是,当一个方法因为它的大小而不能内联时(声明final将使该大小缩小几个字节)。

还有两个最后的问题需要解决。首先,当一个方法是final时(从JIT的角度来看),这样的方法是单形的——JVM最喜欢这种方法。

然后是最后一个实例变量(必须在每个构造函数中设置);这些是重要的,因为它们将保证一个正确发布的引用,正如这里提到的一点,也由JLS精确指定。


话虽如此,这里还有一件事是每个答案都看不见的:垃圾收集。这将花费大量时间来解释,但是当您读取一个变量时,GC对该读取有一个所谓的障碍。每个loadad和getField都通过这样的屏障“保护”,这里有更多细节。理论上,最终场不需要这样的“保护”(它们可以完全跳过屏障)。因此,如果GC这样做- final将提高性能。

简单的回答:不用担心!

长一点的回答:

在讨论final局部变量时,请记住使用关键字final将帮助编译器静态优化代码,这可能最终导致更快的代码。例如,下面示例中的最终字符串a + b是静态连接的(在编译时)。

public class FinalTest {

    public static final int N_ITERATIONS = 1000000;

    public static String testFinal() {
        final String a = "a";
        final String b = "b";
        return a + b;
    }

    public static String testNonFinal() {
        String a = "a";
        String b = "b";
        return a + b;
    }

    public static void main(String[] args) {
        long tStart, tElapsed;

        tStart = System.currentTimeMillis();
        for (int i = 0; i < N_ITERATIONS; i++)
            testFinal();
        tElapsed = System.currentTimeMillis() - tStart;
        System.out.println("Method with finals took " + tElapsed + " ms");

        tStart = System.currentTimeMillis();
        for (int i = 0; i < N_ITERATIONS; i++)
            testNonFinal();
        tElapsed = System.currentTimeMillis() - tStart;
        System.out.println("Method without finals took " + tElapsed + " ms");

    }

}

结果呢?

Method with finals took 5 ms
Method without finals took 273 ms

在Java Hotspot VM 1.7.0_45-b18上测试。

那么实际性能提高了多少呢?我不敢说。在大多数情况下可能是微不足道的(在这个合成测试中大约为270纳秒,因为完全避免了字符串连接——这种情况很少见),但在高度优化的实用程序代码中,它可能是一个因素。在任何情况下,对最初问题的答案都是肯定的,它可能会提高性能,但充其量只能提高一点点。

除了编译时的好处,我找不到任何证据表明使用关键字final对性能有任何可衡量的影响。