在Java中使用final关键字是否能提高性能?

412

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

比如:

String str = "abc";
System.out.println(str);
在上述情况下,`str` 可以是 `final` 的,但通常会省略。当一个方法永远不会被覆盖时,我们可以使用 `final` 关键字。同样地,在不会被继承的类的情况下也可以使用。是否在这些情况下使用 `final` 关键字真正提高了性能?如果是这样,那么它是如何做到的?请解释一下。如果适当使用 `final` 关键字确实对性能有影响,那么 Java 程序员应该养成什么样的习惯来最好地利用这个关键字?

5
我觉得这是一个典型的考试问题。我记得期末成绩确实对表现有影响,如果我没记错的话,final类可以在某种程度上通过JRE进行优化,因为它们不能被子类化。 - Kawu
我实际上已经测试过了。在我测试的所有JVM上,使用final关键字修饰局部变量确实提高了性能(虽然略微,但仍然可能是实用方法中的一个因素)。源代码在我的下面回答中。 - rustyx
在进行性能检查时,最好使用像Caliper这样的工具来进行微基准测试。 - Archimedes Trajano
唯一需要关注使用final的性能收益的情况是:您有一些非常小的方法,例如“long xor(long v1,long v2){return v1 ^ v2;};”,而这个方法占用了相当大的CPU时间。通过将v1和v2标记为final,您可以避免不必要的变量复制(显然,这比操作本身需要更多的时间)。 - Ivan Zaitsau
3
"premature optimization is the root of all evil". 让编译器自己去做它的工作吧。写出易读且有注释的代码,这总是最好的选择! - kaiser
显示剩余3条评论
14个回答

1

我不是专家,但我认为如果类或方法不会被覆盖,你应该将final关键字添加到其中,并保持变量不变。如果有任何优化这些事情的方式,编译器会为你完成。


1

实际上,在测试一些与OpenGL相关的代码时,我发现在私有字段上使用final修饰符可能会降低性能。以下是我测试的类的开头:

public class ShaderInput {

    private /* final */ float[] input;
    private /* final */ int[] strides;


    public ShaderInput()
    {
        this.input = new float[10];
        this.strides = new int[] { 0, 4, 8 };
    }


    public ShaderInput x(int stride, float val)
    {
        input[strides[stride] + 0] = val;
        return this;
    }

    // more stuff ...

这是我用来测试各种替代方案性能的方法,其中包括ShaderInput类:

public static void test4()
{
    int arraySize = 10;
    float[] fb = new float[arraySize];
    for (int i = 0; i < arraySize; i++) {
        fb[i] = random.nextFloat();
    }
    int times = 1000000000;
    for (int i = 0; i < 10; ++i) {
        floatVectorTest(times, fb);
        arrayCopyTest(times, fb);
        shaderInputTest(times, fb);
        directFloatArrayTest(times, fb);
        System.out.println();
        System.gc();
    }
}

第三次迭代后,虚拟机已经热身,我始终得到这些数字没有最终关键字:

Simple array copy took   : 02.64
System.arrayCopy took    : 03.20
ShaderInput took         : 00.77
Unsafe float array took  : 05.47

使用final关键字:
Simple array copy took   : 02.66
System.arrayCopy took    : 03.20
ShaderInput took         : 02.59
Unsafe float array took  : 06.24

注意ShaderInput测试的数字。
无论我将字段设置为公共或私有都没有关系。
顺便说一下,还有一些令人困惑的事情。ShaderInput类在性能上优于所有其他变体,即使使用final关键字也是如此。这很显著,因为它基本上是一个包装浮点数组的类,而其他测试直接操作数组。必须弄清楚这个问题。可能与ShaderInput的流畅界面有关。
此外,System.arrayCopy对于小型数组实际上比在for循环中从一个数组复制元素到另一个数组要慢一些。使用sun.misc.Unsafe(以及直接的java.nio.FloatBuffer,在此处未显示)表现极差。

1
你忘记将参数设为final了。<pre><code> public ShaderInput x(final int stride, final float val) { input[strides[stride] + 0] = val; return this; } </code></pre> 根据我的经验,将任何变量或字段设置为final确实可以提高性能。 - Anticro
1
<pre><code> 最后,也要将其他的变量都设为final: final int arraySize = 10; final float[] fb = new float[arraySize]; for (int i = 0; i < arraySize; i++) { fb[i] = random.nextFloat(); } final int times = 1000000000; for (int i = 0; i < 10; ++i) { floatVectorTest(times, fb); arrayCopyTest(times, fb); shaderInputTest(times, fb); directFloatArrayTest(times, fb); System.out.println(); System.gc(); } </code></pre> - Anticro

0

我不能说这是不常见的,因为据我所知这是在Java中声明常量的唯一方法。作为一个JavaScript开发者,我知道关键字constant有多么重要。如果你正在生产级别上工作,并且你有一些值永远不会被其他程序员意外更改,例如社会安全号码甚至姓名,那么你必须使用final关键字来声明变量。有时候,如果某些类可以被继承,这可能会非常麻烦。因为如果许多人在团队中工作,有人可以继承一个类,扩展它,甚至对父类的变量和方法进行更改。这可以通过关键字final来阻止,因为即使静态变量也可以更改,除非使用了final关键字。就你的问题而言,我认为final关键字不会影响代码的性能,但它肯定可以通过确保其他团队成员不会意外修改任何需要保持不变的内容来防止人为错误。


-4

final 关键字在 Java 中有五种用法。

  1. 类是 final 的
  2. 引用变量是 final 的
  3. 局部变量是 final 的
  4. 方法是 final 的

类是 final 的:类是 final 的意思是我们不能扩展或继承,也就是说继承不可能。

同样地 - 对象是 final 的:有时我们不修改对象的内部状态,因此在这种情况下,我们可以指定对象为 final 对象。对象 final 意味着不是变量也是 final。

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


2
“对象是final”意味着“对象是不可变的”。 - gyorgyabraham
6
你说得对,但你没有回答问题。 OP并没有问final是什么意思,而是问使用final是否会影响性能。 - Honza Zidek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接