java基礎-關鍵字final

七印miss發表於2019-01-13

概述

final是java中一個保留的關鍵字,也被視為一個修飾符(Modifier),可以修飾一個成員變數、方法引數、方法甚至一個類。

final變數(域)

凡是用final關鍵字修飾的成員變數或本地變數都可以稱為final變數,即常量。被final修飾後的變數是只讀的,即一旦初始化後就不允許重新賦值。但如果final修飾的是一個物件的引用,則物件本身的內容是可以改變的,不可改變的是final物件不允許再指向其他物件。 在修飾變數時,final經常和其好基友final一起出現,如宣告一個日誌物件時:

private final static Logger LOG = LoggerFactory.getLogger(Abc.class);
複製程式碼

LOG被定義為static,使得這個變數與當前類Abc繫結,避免每次都new一個新物件,造成資源浪費;同時LOG被定義成final,可以有效防止無意中將LOG變數重新賦值。
在包括Math在內的工具類中,一般都包含很多final域,如:

public final class Math {
    ...
    public static final double E = 2.7182818284590452354;
    public static final double PI = 3.14159265358979323846;
    ...
}
複製程式碼

常量分類

  1. 全域性常量:類的公開靜態常量,使用public static final修飾
  2. 類內常量:類的私有靜態屬性,使用private static final修飾
  3. 區域性常量:包括方法常量和引數常量,前者包括方法體內和程式碼塊內定義的常量。

命名規約:

  1. 全域性常量和類內常量使用字母全部大寫、單詞之間加下劃線的方式
  2. 區域性變數採用小駝峰形式

final入參

首先需要明確一點java中只有按值傳遞,沒有按引用傳遞

基本型別引數

對於基本資料型別,按值傳遞呼叫函式時並不會改變在原函式中的值。所以使用final修飾的目的不是為了防止呼叫函式對原數值造成副作用,而僅僅是為了防止使用者無意間對入參重新賦值,而可能引發的一些難以發現的BUG。

引用型別引數

與基本型別引數相似,使用final修飾引用型別入參時,如果在方法內對該變數重新賦值,會導致編譯失敗。

public static void changeMe(int i){
    i = 123; // OK,但不會更改實參的值
}

public static void changeMe(final int i) {
    i = 123; // 編譯失敗
}

public static void changeMe(BookBean book) {
    book = new BookBean(); // OK,但是一般不會這樣使用,沒啥意義。最好為新new的物件啟用一個新的變數名
}

public static void changeMe(final BookBean book){
    book = new BookBean(); // 編譯失敗
}
複製程式碼

final方法

使用final修飾的方法,不能被子類override。意味著你認為這個方法已經很nice了,子類沒有必要再去修改了。從效能角度考慮,final方法相比非final方法要更快,因為前者是在編譯階段就已經被靜態繫結了,不需要在執行期間再去動態繫結。java.lang.Object類中大部分方法都是final修飾的,如:

public final native Class<?> getClass();
public final native void notify();
複製程式碼

final類

final方法是不能被子類重寫,相似地,final類則表明該類不能被子類繼承。java中有許多類都是被final修飾的,如String類、包裝類、Objects。這些類都屬於不可變類,即只能建立只讀物件 。如果我們嘗試去extends一個String類,編譯器就不會同意。

// String類定義
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence { ... };
// Integer類定義
public final class Integer
    extends Number implements Comparable<Integer> { ... }
// Objects類定義
public final clas Objects { ... }
複製程式碼

效率與安全

  1. 當final變數是基本資料型別以及String型別時,如果在編譯期間能知道它的確切值,則編譯器會把它當做編譯期常量使用。也就是說在用到該final變數的地方,相當於直接訪問的這個常量,不需要在執行時確定。
  2. 不可變類可以提供一些靜態方法,他們把頻繁被請求的例項緩存起來,從而當現有實現可以符合請求的時候,就不必建立新的例項。所有基本型別的包裝型別和BigInteger都有這樣的靜態工廠。使用這樣的靜態工廠也使得客戶端之間可以共享現有的例項,從而降低記憶體佔用率和GC的回收成本。如Integer的內部類IntegerCache就提供了例項的快取機制。詳見Integer的內部類IntegerCache
  3. final修飾的不可變類建立的不可變物件是執行緒安全的,它們不要求同步。當多個執行緒併發訪問這樣的物件時它們不會被破壞,這無疑是獲得執行緒安全最容易的辦法,即不可變物件可以被自由地共享。--《Effective Java》

20190119更新:常量的分類及命名規約

相關文章