Java裡final關鍵字,老生常談的幾個點很多人都應該知道,你不遵循下面的定義,編譯器都會報錯不給通過。
fianl修飾的類是不能被繼承的final修飾的方法是不能被子類重寫的final修飾的變量是不可變的
final修飾的引用類型變量
關於第3條,這個變量不可變,假如變量是引用型變量,指的是這個變量在內存中的地址不可變,並不是變量本身元素不可變,來看代碼示例
final修飾的變量要麼在聲明時就要初始化,要麼在構建方法裡初始化,不然編譯器也會報錯基本類型i,我在read方法裡改變它的值編譯器會報錯,因為final修飾的不可變引用類型array,我在構造方法裡指定它裡面元素是1,然後在read方法裡更改元素是2
看到沒array這個數組的元素是可以改的。
final域的重排序規則
對於final域,編譯器和處理器要遵守兩個重排序規則。
在構造函數內對一個final域的寫入,與隨後把這個被構造對象的引用賦值給一個引用變量,這兩個操作不能重排序初次讀一個包含final域的對象的引用,與隨後初次讀這個final,這兩個操作不能重排序
final域這兩條指令重排序能確保在多線程的環境裡,i和array都能正確被初始化,其他線程在讀到obj這個對象的final域(比如array)時,一定先讀包含final域對象的引用,而不會讀成null。
final域指令重排序實現方式與總結
在final域寫之後,構造方法return之前插入StoreStore內存屏障,在讀final域之前插入LoadLoad內存屏障,這樣可以確保final域的讀寫不會被重排序到對象構造方法之外,確保final域能被正確初始化。對內存屏障不清楚的同學可以看下這篇深入分析volatile是如何實現可見性和有序性的
面試題:String 為什麼是final的
先看下String的定義
它的內部有個本地intern()方法
對於這個面試題每個人都有自己的理解,我這邊給出自己的幾點參考。
Java把String設計為常量,放在字符串常量池裡,被final修飾後不可變,多個線程只有讀沒有寫操作,就是線程安全的。它裡面有hash屬性,因為字符串是不可變的,所以在它創建的時候hashcode就被緩存了,不需要重新計算。這就使得字符串很適合作為Map中的鍵字符串池的實現可以在運行時節約很多heap空間,因為不同的字符串變量都指向池中的同一個字符串。但如果字符串是可變的,那麼String intern()將不能實現(String intern()是指對不同的字符串僅僅只保存一個,即不會保存多個相同的字符串。),因為這樣的話,如果變量改變了它的值,那麼其它指向這個值的變量的值也會一起改變。