簡介
final是java的關鍵字,可以聲明成員變量、方法、類以及本地變量,它所表示的是“這部分是無法修改的”。不想被改變的原因有兩個:效率、設計。
final作用于方法
final 修飾方法,則表明該方法不能被重寫(override),是以對于 final 方法使用的第一個原因是針對設計的,進行方法鎖定,以防止任何子類來對它的修改.
final 方法, 在某些情況下可以對執行效率産生幫助.對于被修飾為final的方法,在編譯器期的時候,有可能會進行
内聯(inline)
優化.
内聯調用:
是編譯器為程式做的一種優化操作.虛拟機不再執行正常的方法調用(參數壓棧,跳轉到方法處執行,再調回,處理棧參數,處理傳回值),而是直接将方法展開,以方法體中的實際代碼替代原來的方法調用。這樣減少了方法調用的開銷。
- 如果方法被多次調用,或者内聯的方法将會被多次拷貝,會相應的增加記憶體占用. 這是一種空間置換時間的一個政策.
- 如果方法體代碼量過大,拷貝的次數過多,那麼将反而達不到優化的目的.
- 對于final方法是否進行内聯,由編譯器決定,并不是所有的final方法都會被内聯.
- 編譯器進行内聯優化,并不隻針對final方法, 如單行實作的方法也可能被内聯.
final作用于類
如果某個類用 final 修改,表明該類是最終類,它不希望也不允許其他來繼承它。在程式設計中處于安全或者其他原因,我們不允許該類存在任何變化,也不希望它有子類,這個時候就可以使用 final 來修飾該類了.
final修飾的類,其成員方法也會自動加上final修飾,而成員變量不受影響.
final作用于變量
final修飾變量分為兩種情況, 一種是作用于基本資料類型;一種是作用于引用類型.
- 作用于基本資料類型
表示該變量的值不能被修改,在使用
javap -v
反彙編後,可以發現它被标注為
ConstantValue
static final java.lang.String sfs;
descriptor: Ljava/lang/String;
flags: ACC_STATIC, ACC_FINAL
ConstantValue: String xxx
- 作用在引用類型
表示該對象的引用不能被更改.即該對象初始化後,不能在對其指派為其他引用. 但是其引用的對象内容可以被更改.
final 對用于成員變量(Filed)在并發中作用
final的記憶體語義 : 隻要對象是正确構造的(被構造對象的引用在構造函數中沒有“逸出”),那麼不需要使用同步(指lock和volatile的使用)就可以保證任意線程都能看到這個final域在構造函數中被初始化之後的值。
final域的重排序規則
- 在構造函數内對一個final域的寫入,與随後把這個被構造對象的引用指派給一個引用變量,這兩個操作之間不能重排序。
JMM禁止編譯器把final域的寫重排序到構造函數之外.
編譯器會在final域的寫之後,構造函數return之前,插入一個StoreStore屏障。這個屏障禁止處理器把final域的寫重排序到構造函數之外。
- 初次讀取一個包含final域的對象的引用,與随後初次讀這個final域,這兩個操作之間不能重排序。
在一個線程中,初次讀對象引用與初次讀該對象包含的final域,JMM禁止處理器重排序這兩個操作(注意,這個規則僅僅針對處理器)。
編譯器會在讀final域操作的前面插入一個LoadLoad屏障。
構造函數"逸出" : 在構造函數内部,這個被構造對象的引用為其他線程所見.如構造函數中将this指派給成員變量.
public class FinalReference {
final int i;
static FinalReference obj;
public FinalReference() {
i = 1; // 1. 寫final域
obj = this; // 2. this引用在此"逸出"
}
public static void writer() {
new FinalReference();
}
public static void reader() {
if (obj != null) { // 3.
int temp = obj.i; // 4.
}
}
}
構造函數"逸出",将不能保證final語義.
在構造函數傳回前,被構造對象的引用不能為其他線程所見,因為此時的final域可能還沒有被初始化。