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

例如:

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

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

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

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


当前回答

正如在其他地方提到的,“final”用于局部变量,在较小程度上用于成员变量,更多的是一种风格问题。

'final'是一个声明,你希望变量不变(即,变量不会变化!)然后,如果你违反了自己的约束,编译器可以通过报错来帮助你。

我也认为,如果标识符(对不起,我只是不能把一个不变的东西称为“变量”)默认为final,并且要求你显式地说它们是变量,那么Java将是一种更好的语言。但话虽如此,我通常不会在初始化且从未赋值的局部变量上使用'final';好像太吵了。

(我确实在成员变量上使用final)

其他回答

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

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

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

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

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

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

编辑:

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

final关键字在Java中有五种使用方式。

一门课是最终的 引用变量是最终变量 局部变量是final 方法是最终的

类是最终的:类是最终的意味着我们不能被扩展,继承意味着继承是不可能的。

类似地-一个对象是最终对象:有时我们不修改对象的内部状态,所以在这种情况下,我们可以指定对象是最终对象。Object final意味着不是变量也是final。

一旦引用变量成为final,它就不能被重新分配给其他对象。但是可以改变对象的内容,只要它的字段不是final的

根据IBM的说法——它不用于类或方法。

http://www.ibm.com/developerworks/java/library/j-jtp04223.html

正如在其他地方提到的,“final”用于局部变量,在较小程度上用于成员变量,更多的是一种风格问题。

'final'是一个声明,你希望变量不变(即,变量不会变化!)然后,如果你违反了自己的约束,编译器可以通过报错来帮助你。

我也认为,如果标识符(对不起,我只是不能把一个不变的东西称为“变量”)默认为final,并且要求你显式地说它们是变量,那么Java将是一种更好的语言。但话虽如此,我通常不会在初始化且从未赋值的局部变量上使用'final';好像太吵了。

(我确实在成员变量上使用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将提高性能。