1.概述
final
关键字的性能优势是一个非常热门的辩论主题。根据我们在哪里使用它, final
关键字可以具有不同的目的和不同的性能含义。
在本教程中,我们将探讨在代码中final
我们将研究在变量,方法和类级别final
除了性能,我们还将提到使用final
关键字的设计方面。最后,我们将建议是否以及出于什么原因使用它。
2.局部变量
将final
应用于局部变量时,其值只能必须分配一次。
我们可以在声明final变量或类构造函数中分配值。如果我们稍后尝试更改最终变量值,则编译器将引发错误。
2.1 性能测试
让我们看看将final
关键字应用于我们的局部变量是否可以提高性能。
我们将使用JMH工具来衡量基准测试方法的平均执行时间。在我们的基准测试方法中,我们将对非最终局部变量进行简单的字符串连接:
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public static String concatNonFinalStrings() {
String x = "x";
String y = "y";
return x + y;
}
接下来,我们将重复相同的性能测试,但这一次是使用最终的局部变量:
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public static String concatFinalStrings() {
final String x = "x";
final String y = "y";
return x + y;
}
为了让JIT编译器优化生效,JMH将负责运行预热迭代。最后,让我们看一下测量的平均性能(以纳秒为单位):
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
在我们的示例中,使用最终局部变量可使执行速度提高2.5倍。
2.2 静态代码优化
字符串连接示例演示了final
关键字如何帮助编译器静态优化代码。
使用非fina
l局部变量,编译器生成以下字节码来连接两个字符串:
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
通过添加final
关键字,我们帮助编译器得出结论,字符串连接结果实际上永远不会改变。因此,编译器可以完全避免字符串连接,并可以静态优化生成的字节码:
LDC "xy"
ARETURN
我们应该注意,在大多数情况下,将final
添加到我们的局部变量中不会像本例中那样带来显著的性能优势。
3.实例和类变量
我们可以将final
关键字应用于实例或类变量。这样,我们确保它们的值分配只能完成一次。我们可以在实例初始化程序块或构造函数中,在最终实例变量声明时分配值。
static
关键字添加到类的成员变量来声明类变量。此外,通过将final
关键字应用于类变量,我们可以定义一个constant 。我们可以在常量声明时或在静态初始化程序块中分配值:
static final boolean doX = false;
static final boolean doY = true;
让我们编写一个使用这些boolean
常量的条件的简单方法:
Console console = System.console();
if (doX) {
console.writer().println("x");
} else if (doY) {
console.writer().println("y");
}
接下来,让我们boolean
final
关键字,然后比较该类生成的字节码:
- 使用非最终类变量的示例– 76行字节码
- 使用最终类变量(常量)的示例– 39行字节码
通过将final
关键字添加到类变量中,我们再次帮助编译器执行静态代码优化。编译器将简单地将最终类变量的所有引用替换为其实际值。
但是,我们应该注意,像这样的示例很少在现实的Java应用程序中使用。将变量声明为final
只会对实际应用程序的性能产生较小的积极影响。
4.Effectively final
Effectively final变量一词是在Java 8中引入的。如果未明确将变量声明为final变量,但变量的值在初始化后再也不会更改,则实际上是final变量。
有效地使用final变量的主要目的是使lambda能够使用未明确声明为final的局部变量。但是,Java编译器不会像对最终变量那样有效地对Effectively final变量进行静态代码优化。
5.类和方法
当将final
关键字应用于类和方法时,其目的不同。当我们将final
关键字应用于一个类时,则该类不能被子类化。当我们将其应用于方法时,该方法就不能被覆盖。
没有报告将final
应用于类和方法的性能好处。此外,最终类和方法可能给开发人员带来极大的不便,因为它们限制了我们重用现有代码的选择。因此,不顾后果地使用final
可能会损害良好的面向对象设计原则。
创建最终类或方法有一些正当的理由,例如强制执行不变性。但是,**性能优势并不是在类和方法级别上final
**的充分理由。
6.性能与简洁设计
除了性能之外,我们还可以考虑使用final
其他原因。 final
关键字可以帮助提高代码的可读性和可理解性。让我们看一些有关final
如何传达设计选择的示例:
- 最后的课程是设计不变的
- 方法声明为final,以防止子类不兼容
- 方法参数声明为final,以防止产生副作用
- 最终变量在设计上是只读的
因此,我们应该使用final
将设计选择传达给其他开发人员。此外, final
关键字可以作为编译器执行次要性能优化的有用提示。
7.结论
在本文中,我们研究了使用final
关键字的性能优势。在示例中,我们显示了将final
关键字应用于变量可能会对性能产生较小的积极影响。但是,将final
关键字应用于类和方法不会带来任何性能上的好处。
我们证明了与最终变量不同,编译器没有有效地使用最终变量来执行静态代码优化。最后,除了性能之外,我们还研究了在不同级别上final
0 评论