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
關鍵字如何幫助編譯器靜態優化代碼。
使用非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