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

例如:

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

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

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

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


通常不会。对于虚方法,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...


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

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


注意:不是java专家

如果我没有记错我的java,使用final关键字几乎没有办法提高性能。 我一直知道它的存在是为了“好代码”——设计和可读性。


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

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

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

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

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

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

编辑:

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


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


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

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

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

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

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


是的,它可以。下面是一个final可以提高性能的例子:

条件编译是一种不根据特定条件将代码行编译到类文件中的技术。这可以用来删除产品构建中的大量调试代码。

考虑以下几点:

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属性转换为final属性,您已经告诉编译器,无论何时看到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。 这证明了最终变量对性能有影响,至少在这个简单的情况下是这样。


简单的回答:不用担心!

长一点的回答:

在讨论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对性能有任何可衡量的影响。


实际上,在测试一些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();
    }
}

在第三次迭代之后,随着VM的升温,我始终得到了这些没有最后关键字的数据:

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

最后一个关键字:

Simple array copy took   : 02.66
System.arrayCopy took    : 03.20
ShaderInput took         : 02.59
Unsafe float array took  : 06.24

注意ShaderInput测试的图。

将字段设置为公共或私有并不重要。

顺便说一句,还有一些更令人困惑的事情。ShaderInput类的性能优于所有其他变量,即使是final关键字。这是非常了不起的b/c,它基本上是一个包装浮点数组的类,而其他测试直接操作数组。必须把这个弄清楚。可能与ShaderInput的流畅界面有关。

同时系统。arrayCopy对于小数组显然比在for循环中简单地将元素从一个数组复制到另一个数组要慢一些。使用sun.misc.Unsafe(以及直接使用java.nio。FloatBuffer(这里没有显示)执行得非常糟糕。


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

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


Final(至少对于成员变量和参数)更适合人类,而不是机器。

在任何可能的情况下使变量为final是一个很好的实践。我希望Java默认设置“变量”为final,并有一个“可变”关键字来允许更改。不可变类会带来更好的线程代码,只要看一眼每个成员前面都有“final”的类,就会很快显示它是不可变的。

另一种情况——我已经转换了很多代码来使用@NonNull/@Nullable注释(你可以说一个方法参数必须不是null,然后IDE可以警告你每一个地方你传递一个没有@NonNull标记的变量——整个事情蔓延到一个荒谬的程度)。当一个成员变量或形参被标记为final时,证明它不能为空要容易得多,因为你知道它没有在其他任何地方被重新赋值。

我的建议是养成在默认情况下为成员和参数应用final的习惯,它只是几个字符,但如果没有其他的话,将推动您改进您的编码风格。

方法或类的Final是另一个概念,因为它不允许非常有效的重用形式,并且没有真正告诉读者很多东西。最好的使用可能是他们使String和其他内在类型为final的方式,这样你就可以在任何地方依赖一致的行为——这防止了很多错误(尽管有时我很喜欢扩展String ....)哦,可能性)


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

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

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

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


I cannot say it is uncommon because as far as I know it is the only way of declaring a constant in java. As a javascript developer, I know how important the keyword constant is. If you are working in production level, and you have values that can never be changed even accidentally by other coders, values such as SSN number or even names. Then you have to use the final keyword to declare variables. Sometimes it can be very troublesome if certain classes can be inherited. Because if many people are working in a team someone can inherit a class, extend it and even make changes to the variables and methods of the parent class. This can be stopped with the keyword final because even static variables can be changed unless the final keyword is used. As far as your question is concerned I do not think the final keyword can affect the performance of the code, but it can definitely prevent human errors by making sure that other team members do not accidentally modify anything that needs to remain constant.


在Java中,我们使用final关键字使东西不可变,至少有3种方式可以使不可变对代码性能产生真正的影响。这三点有一个共同的起源,使编译器或开发人员做更好的假设:

更可靠的代码 更高效的代码 更高效的内存分配和垃圾收集

更可靠的代码

正如许多其他回复和评论所述,使类不可变会产生更清晰、更可维护的代码,使对象不可变会使它们更容易处理,因为它们可以完全处于一种状态,因此这转化为更容易的并发性和完成任务所需时间的优化。

此外,编译器会警告你使用未初始化的变量,不让你用新值重新赋值。

如果我们谈论方法参数,将它们声明为final,如果你不小心对变量使用了相同的名称,或者重新赋值(使参数不再可访问),编译器会抱怨。

更高效的代码

对生成的字节码进行简单的分析,就可以解决性能问题:使用@rustyx在他的回复中发布的代码的最小修改版本,您可以看到,当编译器知道对象不会改变其值时,生成的字节码是不同的。

这就是代码:

public class FinalTest {

    private static final int N_ITERATIONS = 1000000;

    private static String testFinal() {
        final String a = "a";
        final String b = "b";
        return a + b;
    }

    private static String testNonFinal() {
        String a = "a";
        String b = "b";
        return a + b;
    }
    
    private static String testSomeFinal() {
        final String a = "a";
        String b = "b";
        return a + b;
    }

    public static void main(String[] args) {
        measure("testFinal", FinalTest::testFinal);
        measure("testSomeFinal", FinalTest::testSomeFinal);
        measure("testNonFinal", FinalTest::testNonFinal);
    }
    
    private static void measure(String testName, Runnable singleTest){
        final long tStart = System.currentTimeMillis();
        for (int i = 0; i < N_ITERATIONS; i++)
            singleTest.run();
        final long tElapsed = System.currentTimeMillis() - tStart;
        
        System.out.printf("Method %s took %d ms%n", testName, tElapsed);
    }
    
}

使用openjdk17: javac FinalTest.java编译

然后反编译:javap -c -p FinalTest.class

导致这个字节码:

  private static java.lang.String testFinal();
    Code:
       0: ldc           #7                  // String ab
       2: areturn

  private static java.lang.String testNonFinal();
    Code:
       0: ldc           #9                  // String a
       2: astore_0
       3: ldc           #11                 // String b
       5: astore_1
       6: aload_0
       7: aload_1
       8: invokedynamic #13,  0             // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
      13: areturn

  private static java.lang.String testSomeFinal();
    Code:
       0: ldc           #11                 // String b
       2: astore_0
       3: aload_0
       4: invokedynamic #17,  0             // InvokeDynamic #1:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
       9: areturn

// omitted bytecode for the measure() method, which is not interesting

正如您所看到的,在某些情况下,最后的关键字会产生影响。

为了完整起见,这些是测量的时间:

方法testFinal耗时5毫秒 方法testSomeFinal耗时13毫秒 方法testNonFinal耗时20毫秒

这些时间似乎无关紧要(假设我们完成了100万个任务),但我认为,经过一段时间后,JIT优化正在发挥它的魔力,并消除了差异,但即使这样说,4x也不是可以忽略不计的,因为当涉及到testNonFinal循环时,JVM已经被前面的测试热身了,公共代码还应该优化。

容易内联

更少的字节码也可以转化为更简单、更短的内联,从而更好地利用资源和提高性能。

嵌入式设备

Java开发人员可以编写在服务器、桌面和小型或嵌入式设备上运行的代码,因此在编译时提高代码的效率(并且不完全依赖JVM优化)可以节省所有运行时的内存、时间和精力,并减少并发问题和错误。

更高效的内存分配和垃圾收集

如果对象具有final或immutable字段,那么它们的状态不能改变,并且它们所需要的内存在创建时更容易估计(因此这会导致更少的重定位),并且需要更少的防御性副本:在getter中,我可以直接共享一个不可变对象,而无需创建防御性副本。

最后,还有另一个关于未来可能性的观点:当Valhalla项目看到太阳,“值类”将可用时,将不可变性应用于对象的字段,对于那些想要使用它们的人来说将是一个显著的简化,并利用可能出现的大量jit编译器优化。

一个关于不变性的个人说明

如果变量、对象的属性和方法的参数在Java中默认是不可变的(就像在Rust中一样),开发人员将被迫编写更清晰、性能更好的代码,并且显式地声明所有可以改变其值的对象是可变的,这将使开发人员更加意识到可能出现的错误。

我不知道final类是否也一样,因为可变类听起来对我来说没有什么意义