Java final關鍵字對性能的影響
- java
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關鍵字如何幫助編譯器靜態優化代碼。
使用非final局部變量,編譯器生成以下字節碼來連接兩個字符串:
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