在Java中,我们看到很多地方可以使用final关键字,但它的使用并不常见。
例如:
String str = "abc";
System.out.println(str);
在上面的例子中,str可以是final,但通常不使用。
当一个方法永远不会被覆盖时,我们可以使用final关键字。类似地,类不会被继承。
在任何或所有这些情况下使用final关键字真的能提高性能吗?如果是,那该怎么做?请解释一下。如果final的正确使用确实关系到性能,那么Java程序员应该养成什么样的习惯才能最好地利用这个关键字呢?
简单的回答:不用担心!
长一点的回答:
在讨论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对性能有任何可衡量的影响。
Final(至少对于成员变量和参数)更适合人类,而不是机器。
在任何可能的情况下使变量为final是一个很好的实践。我希望Java默认设置“变量”为final,并有一个“可变”关键字来允许更改。不可变类会带来更好的线程代码,只要看一眼每个成员前面都有“final”的类,就会很快显示它是不可变的。
另一种情况——我已经转换了很多代码来使用@NonNull/@Nullable注释(你可以说一个方法参数必须不是null,然后IDE可以警告你每一个地方你传递一个没有@NonNull标记的变量——整个事情蔓延到一个荒谬的程度)。当一个成员变量或形参被标记为final时,证明它不能为空要容易得多,因为你知道它没有在其他任何地方被重新赋值。
我的建议是养成在默认情况下为成员和参数应用final的习惯,它只是几个字符,但如果没有其他的话,将推动您改进您的编码风格。
方法或类的Final是另一个概念,因为它不允许非常有效的重用形式,并且没有真正告诉读者很多东西。最好的使用可能是他们使String和其他内在类型为final的方式,这样你就可以在任何地方依赖一致的行为——这防止了很多错误(尽管有时我很喜欢扩展String ....)哦,可能性)
实际上,在测试一些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(这里没有显示)执行得非常糟糕。