Javaの最終キーワード–パフォーマンスへの影響
1. 概要
final キーワードを使用することによるパフォーマンス上の利点は、Java開発者の間で非常に人気のある議論のトピックです。 適用する場所に応じて、 finalキーワードは、異なる目的と異なるパフォーマンスへの影響を与える可能性があります。
このチュートリアルでは、コードでfinalキーワードを使用することでパフォーマンス上の利点があるかどうかを調べます。 final を変数、メソッド、およびクラスレベルで使用した場合のパフォーマンスへの影響を見ていきます。
パフォーマンスに加えて、finalキーワードを使用する場合の設計面についても説明します。 最後に、それを使用するかどうか、またどのような理由で使用するかをお勧めします。
2. ローカル変数
final がローカル変数に適用される場合、その値は1回だけ割り当てられる必要があります。
最終的な変数宣言またはクラスコンストラクターで値を割り当てることができます。 後で最終変数値を変更しようとすると、コンパイラーはエラーをスローします。
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;
}
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
この例では、finalローカル変数を使用すると、2.5倍高速な実行が可能になりました。
2.2. 静的コードの最適化
文字列連結の例は、finalキーワードがコンパイラーがコードを静的に最適化するのにどのように役立つかを示しています。
コンパイラは、非最終ローカル変数を使用して、2つの文字列を連結するために次のバイトコードを生成しました。
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キーワードをインスタンス変数またはクラス変数に適用できます。 そうすることで、それらの値の割り当てを1回だけ実行できるようにします。 インスタンス初期化ブロックまたはコンストラクターで、最終インスタンス変数宣言時に値を割り当てることができます。
クラス変数は、staticキーワードをクラスのメンバー変数に追加することによって宣言されます。 さらに、クラス変数にfinalキーワードを適用することにより、定数を定義しています。 定数宣言時または静的初期化ブロックで値を割り当てることができます。
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");
}
次に、 finalキーワードをboolean クラス変数から削除し、クラスで生成されたバイトコードを比較してみましょう。
- 非最終クラス変数の使用例–76行のバイトコード
- 最終クラス変数(定数)の使用例–39行のバイトコード
final キーワードをクラス変数に追加することにより、コンパイラが静的コード最適化を実行するのを再び支援しました。 コンパイラは、最終クラス変数のすべての参照を実際の値に置き換えるだけです。
ただし、このような例が実際のJavaアプリケーションで使用されることはめったにないことに注意してください。 変数をfinalとして宣言すると、実際のアプリケーションのパフォーマンスにわずかなプラスの影響しか与えられません。
4. 事実上最終
事実上final変数という用語は、Java8で導入されました。 変数が明示的にfinalとして宣言されていない場合、変数は事実上finalですが、初期化後にその値が変更されることはありません。
効果的にfinal変数の主な目的は、ラムダが明示的にfinalと宣言されていないローカル変数を使用できるようにすることです。 ただし、Java コンパイラは、最終変数の場合のように、効果的に最終変数の静的コード最適化を実行しません。
5. クラスとメソッド
final キーワードは、クラスとメソッドに適用される場合、異なる目的を持ちます。 final キーワードをクラスに適用すると、そのクラスをサブクラス化できなくなります。 それをメソッドに適用すると、そのメソッドをオーバーライドすることはできません。
finalをクラスとメソッドに適用することによるパフォーマンス上の利点は報告されていません。 さらに、最終的なクラスとメソッドは、既存のコードを再利用するためのオプションを制限するため、開発者にとって大きな不便の原因となる可能性があります。 したがって、 final を無謀に使用すると、オブジェクト指向の優れた設計原則が損なわれる可能性があります。
不変性を強制するなど、最終的なクラスまたはメソッドを作成する正当な理由がいくつかあります。 ただし、パフォーマンスの利点は、クラスおよびメソッドレベルでfinalを使用する理由にはなりません。
6. パフォーマンスと すっきりとしたデザイン
パフォーマンスに加えて、finalを使用する他の理由を考慮する場合があります。 final キーワードは、コードの可読性と理解性を向上させるのに役立ちます。 finalがデザインの選択をどのように伝えることができるかのいくつかの例を見てみましょう。
- 最終クラスは、拡張を防ぐための設計上の決定です。これは、不変オブジェクトへのルートである可能性があります。
- 子クラスの非互換性を防ぐために、メソッドはfinalとして宣言されます
- メソッドの引数は、副作用を防ぐためにfinalとして宣言されます
- 最終変数は設計上読み取り専用です
したがって、設計の選択を他の開発者に伝達するためにfinalを使用する必要があります。 さらに、変数に適用される final キーワードは、コンパイラーがマイナーなパフォーマンスの最適化を実行するための有用なヒントとして役立ちます。
7. 結論
この記事では、finalキーワードを使用することによるパフォーマンス上の利点について説明しました。 例では、変数にfinalキーワードを適用すると、パフォーマンスにわずかなプラスの影響を与える可能性があることを示しました。 それでも、 final キーワードをクラスとメソッドに適用しても、パフォーマンスが向上することはありません。
最終変数とは異なり、静的コードの最適化を実行するためにコンパイラが最終変数を効果的に使用しないことを示しました。 最後に、パフォーマンスに加えて、最終的なキーワードをさまざまなレベルに適用することの設計上の影響を調べました。
いつものように、ソースコードはGitHubでから入手できます。