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

例如:

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

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

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

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


当前回答

实际上,在测试一些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(这里没有显示)执行得非常糟糕。

其他回答

实际上,在测试一些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(这里没有显示)执行得非常糟糕。

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

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

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

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

注意:不是java专家

如果我没有记错我的java,使用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是一个很好的实践。我希望Java默认设置“变量”为final,并有一个“可变”关键字来允许更改。不可变类会带来更好的线程代码,只要看一眼每个成员前面都有“final”的类,就会很快显示它是不可变的。

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

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

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