在Java中使用final关键字可以提高性能吗?
在Java中,我们看到很多可以使用final
关键字的地方,但它的使用并不常见。
例如:
String str = "abc";
System.out.println(str);
在上面的例子中, str
可以是final
但这通常是中止的。
当一个方法永远不会被覆盖时,我们可以使用final关键字。 同样的情况下,一个类不会被继承。
在任何或所有这些情况下使用final关键字是否真的可以提高性能? 如果是这样,那么怎么样? 请解释。 如果正确使用final
对于性能确实非常重要,那么Java程序员应该开发哪些习惯来充分利用关键字?
通常不会。 对于虚拟方法,HotSpot会跟踪方法是否已被实际覆盖,并且能够执行优化,比如假设方法没有被覆盖,直到它加载一个覆盖方法的类,此时它可以撤销(或部分撤消)这些优化。
(当然,这是假设你正在使用HotSpot--但它是迄今为止最常见的JVM,所以......)
在我看来,您应该根据清晰的设计和可读性使用final
,而不是出于性能原因。 如果出于性能原因想要更改任何内容,则应在将最清晰的代码弯曲变形之前执行适当的测量 - 通过这种方式,您可以决定是否有任何额外的性能值得阅读/设计较差。 (根据我的经验,这几乎是不值得的; YMMV。)
编辑:作为最后的领域已被提及,值得提出的是,无论如何,他们往往是一个好主意,在清晰的设计方面。 它们还根据跨线程可见性更改了保证的行为:构造函数完成后,任何最终字段都将保证在其他线程中立即可见。 根据我的经验,这可能是final
中最常见的用法,尽管作为Josh Bloch的“继承或禁止设计的经验”的支持者,我应该更经常地使用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
关键词对性能有任何可衡量的影响。
编辑:当使用javap -c FinalTest.class
反编译代码时,获得以下指令:
public static java.lang.String testFinal();
Code:
0: ldc #16 // String a
2: astore_0
3: ldc #18 // String b
5: astore_1
6: ldc #20 // String ab
8: areturn
public static java.lang.String testNonFinal();
Code:
0: ldc #16 // String a
2: astore_0
3: ldc #18 // String b
5: astore_1
6: new #24 // class java/lang/StringBuilder
9: dup
10: aload_0
11: invokestatic #26 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
14: invokespecial #32 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
17: aload_1
18: invokevirtual #35 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #39 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: areturn
由于testFinal()
方法的指令少于testNonFinal()
,因此速度会更快。
是的,它可以。 这是一个最终可以提升性能的例子:
条件编译是一种技术,根据特定条件,代码行不会编译到类文件中。 这可以用来删除生产版本中的大量调试代码。
考虑以下:
public class ConditionalCompile {
private final static boolean doSomething= false;
if (doSomething) {
// do first part.
}
if (doSomething) {
// do second part.
}
if (doSomething) {
// do third part.
}
if (doSomething) {
// do finalization part.
}
}
通过将doSomething属性转换为最终属性,您告诉编译器,只要它看到doSomething,就应该按照编译时替换规则将其替换为false。 编译器的第一遍将代码更改为如下所示:
public class ConditionalCompile {
private final static boolean doSomething= false;
if (false){
// do first part.
}
if (false){
// do second part.
}
if (false){
// do third part.
}
if (false){
// do finalization part.
}
}
一旦完成,编译器会再次查看它,并发现代码中存在无法访问的语句。 由于您正在使用高质量的编译器,因此它不喜欢所有那些无法访问的字节码。 所以它将它们删除,最终得到这个结果:
public class ConditionalCompile {
private final static boolean doSomething= false;
public static void someMethodBetter( ) {
// do first part.
// do second part.
// do third part.
// do finalization part.
}
}
从而减少任何过多的代码或任何不必要的条件检查。
编辑:作为一个例子,我们来看下面的代码:
public class Test {
public static final void main(String[] args) {
boolean x = false;
if (x) {
System.out.println("x");
}
final boolean y = false;
if (y) {
System.out.println("y");
}
if (false) {
System.out.println("z");
}
}
}
在使用Java 8编译此代码并使用javap -c Test.class
反编译时,我们得到:
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public static final void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: ifeq 14
6: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #22 // String x
11: invokevirtual #24 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: iconst_0
15: istore_2
16: return
}
我们可以注意到,编译后的代码只包含非最终变量x
。 这证明最终变量对性能有影响,至少对于这种简单的情况。
上一篇: Does use of final keyword in Java improve the performance?