概述
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;
...
}
複製程式碼
常量分類
- 全域性常量:類的公開靜態常量,使用
public static final
修飾 - 類內常量:類的私有靜態屬性,使用
private static final
修飾 - 區域性常量:包括方法常量和引數常量,前者包括方法體內和程式碼塊內定義的常量。
命名規約:
- 全域性常量和類內常量使用字母全部大寫、單詞之間加下劃線的方式
- 區域性變數採用小駝峰形式
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 { ... }
複製程式碼
效率與安全
- 當final變數是基本資料型別以及String型別時,如果在編譯期間能知道它的確切值,則編譯器會把它當做編譯期常量使用。也就是說在用到該final變數的地方,相當於直接訪問的這個常量,不需要在執行時確定。
- 不可變類可以提供一些靜態方法,他們把頻繁被請求的例項緩存起來,從而當現有實現可以符合請求的時候,就不必建立新的例項。所有基本型別的包裝型別和BigInteger都有這樣的靜態工廠。使用這樣的靜態工廠也使得客戶端之間可以共享現有的例項,從而降低記憶體佔用率和GC的回收成本。如Integer的內部類IntegerCache就提供了例項的快取機制。詳見Integer的內部類IntegerCache。
- final修飾的不可變類建立的不可變物件是執行緒安全的,它們不要求同步。當多個執行緒併發訪問這樣的物件時它們不會被破壞,這無疑是獲得執行緒安全最容易的辦法,即不可變物件可以被自由地共享。--《Effective Java》
20190119更新:常量的分類及命名規約