一般咱們對Java中final關鍵字的理解是「用final修飾的變量是不可變的」,若是嘗試對final變量屢次賦值,編譯器將報錯。彷佛final的做用就是保證變量不可變,這沒有錯,可是若是咱們在Java中靈活應用final的被修飾目標不可變的特性,每每能發掘出不少使人意想不到的效果,而非僅僅保證變量不可變這麼粗淺而已。下面咱們來講說final關鍵字的多重用法java
用final修飾普通變量一般分爲兩種狀況,修飾普通基本類變量和修飾引用類型變量,也就是對象類型變量。程序員
修飾普通基本類型變量最能清楚直白的表現出final的做用,它能使變量的值沒法改變,由於變量不能再次被賦值。編程
final int myInt = 1; myInt = 2;
運行此代碼,編譯器會報錯:安全
Error:(20, 9) java: 沒法爲最終變量myInt分配值
但咱們使用final修飾引用類型變量時,咱們能夠保證變量不能被再次賦值, 但咱們沒法保證對象值的改變。多線程
final StringBuilder sb = new StringBuilder("Java"); sb.append("Script"); System.out.println(sb); //resultJavaScript
如上代碼所示, 雖然咱們用final修飾變量,但仍舊沒法阻止變量內在值的改變。 使用final能保證變量不能改變引用的目標,卻不能保證變量所引用的目標自己的變化。由於對於基本類型,咱們能夠把變量看做是變量值的自己;而對於引用類型變量,變量和變量的值須要區分看待,它們只是以某種方式被關聯起來了而已,事實上它們是不一樣的東西,因此final沒法同時做用於二者身上。app
2.ide
Java不支持原生常量,在Java種也沒有定義常量的const關鍵字。然而, 咱們可使用final關鍵字間接的實現常量。函數式編程
public static final int CONST_ONE = 1; public static final int CONST_TWO = 2;
常量是全局的、不可變的,所以咱們同時使用static和final來修飾變量,就能達到定義常量的效果。 常量名一般全由大寫字母組成。函數
3.性能
final能夠保證明例變量必須被初始化,這點特性能減小代碼出錯概率,如令全部Java程序員頭疼的NPE。
public class Main { private String name ; @Override public String toString() { return name; } public static void main(String[] arg)throws Exception { Main main = new Main(); System.out.println(main.toString().toLowerCase()); } }
以上代碼由於沒給name賦值,代碼在運行起將報NPE異常。假如咱們使用final修飾name變量,代碼將沒法經過編譯,由於Java語法規定,final變量在使用前必須被初始化,所以咱們必須在構造函數中初始化name變量,這樣能百分百保證咱們使用的name變量不會是null。
public class Main { private final String name; public Main(String name) { this.name = name; } @Override public String toString() { return name; } public static void main(String[] arg)throws Exception { Main main = new Main("Java"); System.out.println(main.toString().toLowerCase()); } } }
4.
final不只能夠修飾變量,還能夠修飾方法和類。
若是咱們用final修飾方法,假如方法所屬的類被繼承,方法將不能在子類中被重寫。
class SuperClass{ protected final String getName() { return 「supper class」; } @Override public String toString() { return getName(); } } classSubClass extends SuperClass{ protected String getName() { return 「sub class」; } }
以上代碼沒法經過編譯,編譯器報錯
Error:(30,22) java: SubClass中的getName()沒法覆蓋SuperClass中的getName() 被覆蓋的方法爲final
由於SuperClass的getName方法被修飾爲final,所以在子類中沒法被重寫。
一般,咱們不但願方法在被繼承時重寫,能夠用private修飾,由於這樣方法的可見性被限制於方法所在的類中。可是,有時候咱們須要公開方法,卻又不想方法被重寫,此時用final修飾方法就有用武之地了。
然而,這時又引出了另一個問題,假如咱們使用final修飾private方法,是否有實際意義。 事實上,在現代的jdk中,這麼作是沒有任何意義的,由於private沒法被繼承,天然也不存在繼承時被修改的問題。 可是在早期的Java版本中,final修飾private方法的做用是告知編譯器,這個方法在編譯時須要內聯處理。這個特性在現代jdk中已經被拋棄。
當用final修飾類時, 表示此類是密封的, 沒法被繼承。從Java源碼中可知,咱們最經常使用的String類即是一個final類。
5.
在haskell、F#之類的函數時語言中,變量值默認就是不可變的,彷彿如Java變量默認就是final同樣, 這種特性能極大的減小代碼出錯的概率。許多極難排查的代碼錯誤,都是因爲狀態改變引發的,即變量值的改變引發的。 若是從源頭杜絕, 就能夠從根本上消滅全部這類錯誤,函數式語言也是基於此考慮才把變量不可變做爲語言的默認特性,因此函數式編程是無狀態的, 這是被證實優勢多餘缺點的一種特性。
此外, 變量的值一旦不可變,在多線程編程的環境下能保證線程安全,由於變量值不可變,也就不存在多個線程同時競爭資源的問題,代碼天然是線程安全的。如String類, 就是以這種模式實現的, 當咱們看到某個字符串被改變, 其實只是生成一個新的字符串而已,舊的字符串並無被修改。固然,這樣作會形成必定的性能問題, 二者間如何權衡,須要開發者根據實際狀況考慮。
根據現代編程的指導原則, 在Java種定義的任何變量,默認都要加上final關鍵字, 這麼作雖然反直覺,卻有好處。退一萬步說,至少能讓代碼的閱讀者瞭解,變量是不可變的, 咱們不用擔憂它會產生反作用。