在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 。 这证明最终变量对性能有影响,至少对于这种简单的情况。

链接地址: http://www.djcxy.com/p/20851.html

上一篇: Does use of final keyword in Java improve the performance?

下一篇: final keyword in method parameters