Java final關鍵字對性能的影響

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