你真的理解final關鍵字的用法嗎?


final 是 Java 中的一個關鍵字,final從字面意思上看 “最終的","不可改變的”。它可以用來修飾變量、方法或者類,而且在修飾不同的地方時,效果、含義和側重點也會有所不同。

(1)final修飾變量,意味着一旦被賦值就不能被修改;

(2)final修飾方法,意味着不能被重寫;

(3)final修飾類,意味着不能被繼承。

(1)final修飾變量

關鍵字 final 修飾變量,意味着這個變量一旦被賦值就不能被修改(只能被賦值一次)。如果我們嘗試對一個已經賦值過 final 的變量再次賦值,就會報編譯錯誤。

為什么要對某個變量去加 final 關鍵字呢?

(1)第一個目的是出於設計角度去考慮的,比如我們希望創建一個一旦被賦值就不能改變的量,那么就可以使用 final 關鍵字。比如聲明常量的時候,通常都是帶 final 的:

public static final String AUTHOR = "WHL";

(2)第二個目的是從線程安全的角度去考慮的。不可變的對象天生就是線程安全的.如果 final 修飾的是基本數據類型,那么它自然就具備了不可變這個性質,所以自動保證了線程安全。

賦值時機

被 final 修飾的變量的賦值時機,變量可以分為以下三種:

(1)成員變量,類中的非 static 修飾的屬性;
(2)靜態變量,類中的被 static 修飾的屬性;
(3)局部變量,方法中的變量。

成員變量

成員變量指的是一個類中的非 static 屬性,對於這種成員變量而言,被 final 修飾后,它有三種賦值時機(或者叫作賦值途徑)。

 需要注意的是,這里講了三種賦值時機,我們必須從中挑一種來完成對 final 變量的賦值。如果是普通變量,可以不用在這三種情況下賦值,完全可以在其他的時機賦值;或者如果你不准備使用這個變量,那么自始至終不賦值甚至也是可以的。但是對於 final 修飾的成員變量而言,必須在三種情況中任選一種來進行賦值,而不能一種都不挑、完全不賦值,那是不行的,這是 final 語法所規定的。否則會報錯如下:

空白 final

下面講解一種概念:“空白 final”。如果我們聲明了 final 變量之后,並沒有立刻在等號右側對它賦值,這種情況就被稱為“空白 final”。這樣做的好處在於增加了 final 變量的靈活性,比如可以在構造函數中根據不同的情況,對 final 變量進行不同的賦值,這樣的話,被 final 修飾的變量就不會變得死板,同時又能保證在賦值后保持不變。我們用下面這個代碼來說明:

/**
 * 描述:     空白final提供了靈活性
 */
public class BlankFinal {

    //空白final
    private final int a;

    //不傳參則把a賦值為默認值0
    public BlankFinal() {
        this.a = 0;
    }

    //傳參則把a賦值為傳入的參數
    public BlankFinal(int a) {
        this.a = a;
    }
}

在這個代碼中,我們有一個 private final 的 int 變量叫作 a,該類有兩個構造函數,第一個構造函數是把 a 賦值為 0,第二個構造函數是把 a 賦值為傳進來的參數,所以你調用不同的構造函數,就會有不同的賦值情況。這樣一來,利用這個規則,我們就可以根據業務去給 final 變量設計更靈活的賦值邏輯。所以利用空白 final 的一大好處,就是可以讓這個 final 變量的值並不是說非常死板,不是絕對固定的,而是可以根據情況進行靈活的賦值,只不過一旦賦值后,就不能再更改了。

靜態變量

靜態變量是類中的 static 屬性,它被 final 修飾后,只有兩種賦值時機。

 需要注意的是,我們不能用普通的非靜態初始代碼塊來給靜態的 final 變量賦值。同樣有一點比較特殊的是,這個 static 的 final 變量不能在構造函數中進行賦值。因為被static修飾的靜態變量為類變量,在類初次加載時會被初始化。只能定義在類的內部、方法的外部,不能在方法中進行聲明,不論是靜態方法還是非靜態方法。而非靜態變量是對象所擁有的,在創建對象的時候通過構造函數初始化。

局部變量

局部變量指的是方法中的變量,如果你把它修飾為了 final,它的含義依然是一旦賦值就不能改變。

但是它的賦值時機和前兩種變量是不一樣的,因為它是在方法中定義的,所以它沒有構造函數,也同樣不存在初始代碼塊,所以對應的這兩種賦值時機就都不存在了。實際上,對於 final 的局部變量而言,它是不限定具體賦值時機的,只要求我們在使用之前必須對它進行賦值即可(方法中的普通變量的要求一樣),否則使用一個未賦值的變量,自然會報錯

 特殊用法:final 修飾參數

關鍵字 final 還可以用於修飾方法中的參數。在方法的參數列表中是可以把參數聲明為 final 的,這意味着我們沒有辦法在方法內部對這個參數進行修改。例如:

/**
 *  final參數
 */
public class FinalPara {
    public void withFinal(final int a) {
        System.out.println(a);//可以讀取final參數的值
//        a = 9; //編譯錯誤,不允許修改final參數的值
    }
}

在這個代碼中有一個 withFinal 方法,而且這個方法的入參 a 是被 final 修飾的。接下來,我們首先把入參的 a 打印出來,這是允許的,意味着我們可以讀取到它的值;但是接下來我們假設想在方法中對這個 a 進行修改,比如改成 a = 9,這就會報編譯錯誤,因此不允許修改 final 參數的值。

以上我們就把 final 修飾變量的情況都講完了,其核心可以用一句話總結:一旦被賦值就不能被修改了。

(2)final 修飾方法

目前我們使用 final 去修飾方法的唯一原因,就是想把這個方法鎖定,意味着任何繼承類都不能修改這個方法的含義,也就是說,被 final 修飾的方法不可以被重寫,不能被 override。我們來舉一個代碼的例子:

 (3)final 修飾類

final 修飾類的含義很明確,就是這個類“不可被繼承”。我們舉個代碼例子:

如果我們這樣設計,就代表不但我們自己不會繼承這個類,也不允許其他人來繼承,它就不可能有子類的出現,這在一定程度上可以保證線程安全。比如非常經典的 String 類就是被 final 修飾的,所以我們自始至終也沒有看到過哪個類是繼承自 String 類的,這對於保證 String 的不可變性是很重要的。但這里有個注意點,假設我們給某個類加上了 final 關鍵字,這並不代表里面的成員變量自動被加上 final。事實上,這兩者之間不存在相互影響的關系,也就是說,類是 final 的,不代表里面的屬性就會自動加上 final。不過我們也記得,final 修飾方法的含義就是這個方法不允許被重寫,而現在如果給這個類都加了 final,那這個類連子類都不會有,就更不可能發生重寫方法的情況。所以,其實在 final 的類里面,所有的方法,不論是 public、private 還是其他權限修飾符修飾的,都會自動的、隱式的被指定為是 final 修飾的

如果必須使用 final 方法或類,請說明原因

這里有一個注意點,那就是如果我們真的要使用 final 類或者方法的話,需要注明原因。為什么呢?因為未來代碼的維護者,他可能不是很理解為什么我們在這里使用了 final,因為使用后,對他來說是有影響的,比如用 final 修飾方法,那他就不能去重寫了,或者說我們用 final 修飾了類,那他就不能去繼承了。所以為了防止后續維護者有困惑,我們其實是有必要或者說有義務說明原因,這樣也不至於發生后續維護上的一些問題。在很多情況下,我們並需要不急着把這個類或者方法聲明為 final,可以到開發的中后期再去決定這件事情,這樣的話,我們就能更清楚的明白各個類之間的交互方式,或者是各個方法之間的關系。所以你可能會發現根本就不需要去使用 final 來修飾,或者不需要把范圍擴得太大,我們可以重構代碼,把 final 應用在更小范圍的類或方法上,這樣造成更小的影響。

為什么String要用final修飾?這樣設計有什么好處呢?

使用 final 修飾的第一個好處是安全;第二個好處是高效

(1)第一個好處是安全,Java 語言之父 詹姆斯·高斯林 (James Gosling),說迫使String類設計成不可變的另一個原因是安全,當你在調用其他方法時,比如調用一些系統級操作指令之前,可能會有一系列校驗,如果是可變類的話,可能在你校驗過后,它的內部的值又被改變了,這樣有可能會引起嚴重的系統崩潰問題,這是迫使 String 類設計成不可變類的一個重要原因。(這是James Gosling在2001年5月的一次訪談中,談到了不可變類和String)

(2)第二個好處是高效,以 JVM 中的字符串常量池來舉例,只有字符串是不可變時,我們才能實現字符串常量池,字符串常量池可以為我們緩存字符串,提高程序的運行效率;

常見面試題

(1) final的三種用法?

(2) 為什么String要用final修飾?為什么被設計為不可變的?這樣設計有什么好處呢?

希望本文章對您有幫助,您的轉發、點贊是我的創作動力,十分感謝。

掃描下方二維碼關注微信公眾號,您會收到更多優質文章推送。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM