在Java中使用final关键字可以提高性能吗?
在Java中我们看到很多地方可以使用final
关键字,但是它的使用是不常见的。
例如:
String str = "abc"; System.out.println(str);
在上面的例子中, str
可以是final
但是这通常是中止的。
当一个方法永远不会被覆盖时,我们可以使用final关键字。 类似的情况下,不会被inheritance的类。
在任何或所有这些情况下使用final关键字是否真正提高性能? 如果是这样,那么怎么样? 请解释。 如果正确使用final
对于性能来说真的很重要,那么Java程序员应该开发哪些习惯来充分利用关键字呢?
通常不会。 对于虚拟方法,HotSpot会跟踪方法是否被实际覆盖,并且能够执行优化,比如假设一个方法没有被覆盖,直到它加载一个覆盖方法的类,在这个点它可以撤销(或部分撤消)这些优化。
(当然,这是假设你正在使用HotSpot–但它是迄今为止最常见的JVM,所以…)
在我看来,你应该使用清晰的devise和可读性而不是出于性能的原因。 如果出于性能原因想要改变任何东西,那么在将最清晰的代码弯曲变形之前,您应该进行适当的测量 – 这样您就可以决定是否有任何额外的性能值得这种较差的可读性/devise。 (根据我的经验,这几乎是不值得的; YMMV。)
编辑:作为最后的领域已经提到,值得提出,无论如何,他们往往是一个好主意,在清晰的devise方面。 它们还会根据跨线程可见性更改保证的行为:在构造函数完成之后,任何最终字段都将保证在其他线程中立即可见。 虽然作为Josh Bloch的“inheritance或禁止devise”的经验的支持者,我可能最常用的是final
。
简短的回答:不要担心!
很长的回答:
在谈到最终的局部variables时,请记住,使用关键字final
可以帮助编译器静态优化代码,最终可能会得到更快的代码。 例如,以下示例中的最后一个stringa + 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上进行testing。
那么实际的性能改善有多大? 我不敢说。 在大多数情况下,这个综合testing可能是微乎其微的(大约270纳秒,因为string级联是完全避免的) – 但是在高度优化的实用程序代码中,这可能是一个因素。 在任何情况下,对原始问题的答案都是肯定的,这可能会提高性能,但最多只能略微提高 。
撇开编译时间的好处,我找不到任何证据表明关键字final
的使用对性能有任何可衡量的影响。
是的,它可以。 这是一个最终可以提高性能的例子:
条件编译是一种将代码行根据特定条件编译到类文件中的技术。 这可以用来删除生产版本中的大量debugging代码。
考虑以下几点:
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,就应该按照编译时replace规则将其replace为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. } }
从而减less任何过多的代码或任何不必要的条件检查。
据IBM介绍,它不适用于类或方法。
http://www.ibm.com/developerworks/java/library/j-jtp04223.html
你真的在问两个(至less)不同的情况:
-
final
是局部variables -
final
的方法/类
Jon Skeet已经回答了2)。 关于1):
我不认为这有什么区别。 对于局部variables,编译器可以推断variables是否是最终的(仅仅通过检查是否被赋值多次)。 所以,如果编译器想优化只分配一次的variables,无论variables是否被实际声明为final
,都可以这样做。
final
可能会对保护/公共职业领域有所作为; 那么编译器就很难知道这个字段是否被设置了多次,因为它可能发生在一个不同的类中(甚至可能没有被加载)。 但即使如此,JVM也可以使用Jon所描述的技术(乐观地优化,如果一个类被加载,会改变字段)。
总之,我看不出有什么理由来帮助业绩。 所以这种微观优化不太可能有帮助。 你可以尝试对其进行基准testing,但是我怀疑它会有所作为。
编辑:
事实上,根据TimoWestkämper的回答, final
可以在某些情况下提高class级领域的performance。 我纠正了。
注意:不是java专家
如果我正确地记得我的java,那么使用final关键字提高性能的方法将会很less。 我一直都知道它存在“好代码” – devise和可读性。
实际上,在testing一些与OpenGL相关的代码时,我发现在私有字段上使用最终修饰符会降低性能。 这是我testing的课程的开始:
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 ...
这是我用来testing各种替代品的性能的方法,其中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
注意ShaderInputtesting的数字。
无论我是公开的还是私人的,我都没有关系。
顺便提一下,还有一些令人困惑的事情。 即使使用final关键字,ShaderInput类也会优于所有其他变体。 这是显着的B / C它基本上是一个包装浮法数组的类,而其他testing直接操纵数组。 必须弄清楚这一个。 可能与ShaderInputstream畅的界面有关。
另外System.arrayCopy显然对于小数组来说显然比在for循环中将元素从一个数组复制到另一个数组慢。 而使用sun.misc.Unsafe(以及直接的java.nio.FloatBuffer,这里没有显示)performance的非常糟糕。
我不是一个专家,但我想你应该添加final
关键字的类或方法,如果它不会被覆盖,并保持单独的variables。 如果有什么办法来优化这些东西的话,编译器会为你做。
肯定是的,如果variables转换为常量,
因为我们知道java编译器将这样的最终variables转换为常量可能的值
作为常量java编译器的概念,在编译时直接用它的引用replace它的值
在java中,如果variables是没有任何运行时进程给出的string或基本types,则它们将变为常量
否则它只是final(不可变)variables,
&constatnt的使用总是比引用更快。
所以如果可能的话,使用任何编程语言的常量来获得更好的性能
final
关键字可以在Java中以五种方式使用。
- 一个class是最后的
- 参考variables是最终的
- 局部variables是最终的
- 一个方法是最终的
一个阶级是最终的:一个阶级是最后的意思,我们不能延续或inheritance意味着inheritance是不可能的。
同样 – 一个对象是final的:有时候我们并没有修改对象的内部状态,所以在这种情况下我们可以指定对象是最终的object.object,最后的意思是不可变也是final。
一旦引用variables是最终的,它不能被重新分配给其他对象。 但只要字段不是最终的,就可以改变对象的内容