1. Overview
1.概述
The performance benefit of using the final keyword is a very popular debate topic among Java developers. Depending on where we apply it, the final keyword can have a different purpose and different performance implications.
使用final关键字的性能优势是Java开发人员中一个非常流行的辩论话题。根据我们应用它的地方,final关键字可以有不同的目的和不同的性能影响。
In this tutorial, we’ll explore if there are any performance benefits from using the final keyword in our code. We’ll look at the performance implications of using final on a variable, method, and class level.
在本教程中,我们将探讨在代码中使用final关键字是否有任何性能优势。我们将研究在变量、方法和类的层面上使用final的性能影响。
Alongside performance, we’ll also mention the design aspects of using the final keyword. Finally, we’ll recommend whether and for what reason we should use it.
除了性能之外,我们还将提到使用final关键字的设计方面。最后,我们将推荐是否应该使用它以及出于什么原因。
2. Local Variables
2.本地变量
When final is applied to a local variable, its value must be assigned exactly once.
当final被应用于一个局部变量时,它的值必须被精确分配一次。
We can assign the value in the final variable declaration or in the class constructor. In case we try to change the final variable value later, the compiler will throw an error.
我们可以在最终变量声明中或在类的构造函数中赋值。如果我们后来试图改变最终变量的值,编译器将抛出一个错误。
2.1. Performance Test
2.1.性能测试
Let’s see if applying the final keyword to our local variables can improve performance.
让我们看看对我们的局部变量应用final关键字是否能提高性能。
We’ll make use of the JMH tool in order to measure the average execution time of a benchmark method. In our benchmark method, we’ll do a simple string concatenation of non-final local variables:
我们将利用JMH工具,以测量一个基准方法的平均执行时间。在我们的基准方法中,我们将做一个简单的非最终局部变量的字符串连接。
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public static String concatNonFinalStrings() {
String x = "x";
String y = "y";
return x + y;
}
Next, we’ll repeat the same performance test, but this time with final local variables:
接下来,我们将重复同样的性能测试,但这次是用最终的局部变量。
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public static String concatFinalStrings() {
final String x = "x";
final String y = "y";
return x + y;
}
JMH will take care of running warmup iterations in order to let the JIT compiler optimizations kick in. Finally, let’s take a look at the measured average performances in nanoseconds:
JMH将负责运行预热迭代,以便让JIT编译器的优化工作启动。最后,让我们来看看以纳秒为单位的测量平均性能。
Benchmark Mode Cnt Score Error Units
BenchmarkRunner.concatFinalStrings avgt 200 2,976 ± 0,035 ns/op
BenchmarkRunner.concatNonFinalStrings avgt 200 7,375 ± 0,119 ns/op
In our example, using final local variables enabled 2.5 times faster execution.
在我们的例子中,使用最终的局部变量使执行速度提高了2.5倍。
2.2. Static Code Optimization
2.2.静态代码优化
The string concatenation example demonstrates how the final keyword can help the compiler optimize the code statically.
字符串连接的例子展示了final关键字如何帮助编译器静态地优化代码。
Using non-final local variables, the compiler generated the following bytecode to concatenate the two strings:
使用非最终的局部变量,编译器生成了以下字节码来连接这两个字符串。
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 0
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ARETURN
By adding the final keyword, we helped the compiler conclude that the string concatenation result will actually never change. Thus, the compiler was able to avoid string concatenation altogether and statically optimize the generated bytecode:
通过添加final关键字,我们帮助编译器得出结论:字符串连接的结果实际上永远不会改变。因此,编译器能够完全避免字符串连接,并静态地优化生成的字节码。
LDC "xy"
ARETURN
We should note that most of the time, adding final to our local variables will not result in significant performance benefits as in this example.
我们应该注意到,大多数时候,在我们的局部变量中添加final并不会像本例中那样带来明显的性能优势。
3. Instance and Class Variables
3.实例和类变量
We can apply the final keyword to the instance or class variables. That way, we ensure that their value assignment can be done only once. We can assign the value upon final instance variable declaration, in the instance initializer block or in the constructor.
我们可以将final关键字应用于实例变量或类变量。这样一来,我们就可以确保它们的赋值只能进行一次。我们可以在最终实例变量声明时,在实例初始化块或构造函数中赋值。
A class variable is declared by adding the static keyword to a member variable of a class. Additionally, by applying the final keyword to a class variable, we’re defining a constant. We can assign the value upon constant declaration or in the static initializer block:
通过在一个类的成员变量上添加static关键字来声明一个类变量。此外,通过将final关键字应用于类变量,我们就定义了一个常量。我们可以在常量声明时或在静态初始化块中赋值。
static final boolean doX = false;
static final boolean doY = true;
Let’s write a simple method with conditions that use these boolean constants:
让我们写一个带有条件的简单方法,使用这些boolean常数。
Console console = System.console();
if (doX) {
console.writer().println("x");
} else if (doY) {
console.writer().println("y");
}
Next, let’s remove the final keyword from the boolean class variables and compare the class generated bytecode:
接下来,让我们把final关键字从boolean类变量中移除,并比较该类生成的字节码。
- Example using non-final class variables – 76 lines of bytecode
- Example using final class variables (constants) – 39 lines of bytecode
By adding the final keyword to a class variable, we again helped the compiler to perform static code optimization. The compiler will simply replace all references of final class variables with their actual values.
通过向类变量添加final关键字,我们再次帮助编译器进行静态代码优化。编译器将简单地把所有对最终类变量的引用替换为其实际值。
However, we should note that an example like this one would rarely be used in real-life Java applications. Declaring variables as final can only have a minor positive impact on the performances of real-life applications.
然而,我们应该注意到,像这样的例子很少会在现实生活中的Java应用程序中使用。将变量声明为final ,只能对现实生活中的应用程序的性能产生微小的积极影响。
4. Effectively Final
4.有效的最终结果
The term effectively final variable was introduced in Java 8. A variable is effectively final if it isn’t explicitly declared final but its value is never changed after initialization.
effectively final变量这一术语是在Java 8中引入的。如果一个变量没有明确声明为最终变量,但它的值在初始化后从未改变,那么该变量就是有效最终变量。
The main purpose of effectively final variables is to enable lambdas to use local variables that are not explicitly declared final. However, the Java compiler won’t perform static code optimization for effectively final variables the way it does for final variables.
有效最终变量的主要目的是使lambdas能够使用未明确声明为最终变量的局部变量。然而,Java 编译器不会像对最终变量那样,对有效最终变量进行静态代码优化。
5. Classes and Methods
5.类和方法
The final keyword has a different purpose when applied to classes and methods. When we apply the final keyword to a class, then that class cannot be subclassed. When we apply it to a method, then that method cannot be overridden.
final关键字在应用于类和方法时有不同的作用。当我们将final关键字应用于一个类时,该类就不能被子类化。当我们把它应用于一个方法时,那么这个方法就不能被重写。
There are no reported performance benefits of applying final to classes and methods. Moreover, final classes and methods can be a cause of great inconvenience for developers, as they limit our options for reusing existing code. Thus, reckless use of final can compromise good object-oriented design principles.
据报道,对类和方法应用final并没有性能上的好处。此外,最终类和方法会给开发者带来极大的不便,因为它们限制了我们对现有代码的重复使用。因此,不顾一切地使用final会损害良好的面向对象设计原则。
There are some valid reasons for creating final classes or methods, such as enforcing immutability. However, performance benefit is not a good reason for using final on class and method levels.
创建final类或方法有一些合理的理由,例如强制执行不可变性。然而,性能优势并不是在类和方法层面上使用final的好理由。
6. Performance vs. Clean Design
6.性能与清洁设计
Besides performance, we might consider other reasons for using final. The final keyword can help improve code readability and understandability. Let’s look into a few examples of how final can communicate design choices:
除了性能,我们还可以考虑使用final的其他原因。final关键字可以帮助提高代码的可读性和可理解性。让我们来看看几个例子,看看final如何传达设计选择。
- final classes are a design decision to prevent extension – this can be a route to immutable objects
- methods are declared final to prevent incompatibility of child classes
- method arguments are declared final to prevent side effects
- final variables are read-only by design
Thus, we should use final for communicating design choices to other developers. Furthermore, the final keyword applied to variables can serve as a helpful hint for the compiler to perform minor performance optimizations.
因此,我们应该使用final来向其他开发者传达设计选择。此外,应用于变量的final关键字可以作为一个有用的提示,让编译器进行小的性能优化。
7. Conclusion
7.结语
In this article, we looked into the performance benefits of using the final keyword. In the examples, we showed that applying the final keyword to variables can have a minor positive impact on performance. Nevertheless, applying the final keyword to classes and methods will not result in any performance benefits.
在这篇文章中,我们研究了使用final关键字的性能优势。在例子中,我们表明将final关键字应用于变量可以对性能产生轻微的积极影响。尽管如此,将final关键字应用于类和方法不会带来任何性能上的好处。
We demonstrated that, unlike final variables, effectively final variables are not used by the compiler for performing static code optimization. Finally, besides performance, we looked at the design implications of applying the final keyword on different levels.
我们证明,与最终变量不同,有效的最终变量不会被编译器用于执行静态代码优化。最后,除了性能,我们还研究了在不同层次上应用final关键字的设计意义。
As always, the source code is available over on GitHub.
一如既往,源代码可在GitHub上获取。