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

例如:

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

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

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

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


当前回答

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

其他回答

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

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

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

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

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

通常不会。对于虚方法,HotSpot会跟踪该方法是否实际被重写,并且能够在假定某个方法没有被重写的情况下执行优化,例如内联——直到它加载一个重写该方法的类,这时它可以撤销(或部分撤销)那些优化。

(当然,这是假设你正在使用HotSpot -但它是迄今为止最常见的JVM,所以…)

在我看来,使用final应该基于清晰的设计和可读性,而不是出于性能考虑。如果您出于性能原因想要更改任何内容,那么应该在修改最清晰的代码之前执行适当的度量——这样您就可以决定以较差的可读性/设计换取额外的性能是否值得。(根据我的经验,这几乎不值得;YMMV)。

EDIT: As final fields have been mentioned, it's worth bringing up that they are often a good idea anyway, in terms of clear design. They also change the guaranteed behaviour in terms of cross-thread visibility: after a constructor has completed, any final fields are guaranteed to be visible in other threads immediately. This is probably the most common use of final in my experience, although as a supporter of Josh Bloch's "design for inheritance or prohibit it" rule of thumb, I should probably use final more often for classes...

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

作为参考,已经在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将提高性能。

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

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