final是Java中的一個關鍵字作岖,final可用于修飾類嵌器、方法枝笨、參數(shù)和變量(包括實例變量和類變量)沦寂。
final修飾類
final修飾的類具有不可繼承性学密,也就是如果一個類是final類型的,則這個類不允許有子類传藏。首先我們頂一個final類:
public final class FinalClass {
private int field;
}
然后如果我們嘗試去繼承這個類的話編譯器會報錯:
public class FalseExtension extends FinalClass {
}
編譯器提示我們:cannot extend final class腻暮,也就是說final類型的類不允許繼承。
final修飾方法
final修飾的方法具有不可變性漩氨,也就是說final的方法不允許在子類中被覆寫(@Override)西壮。下面我們來看一個反例:
在Base子類Extension中我們覆寫(Override)了子類中的final方法,編譯器提示錯誤:final方法不能被覆寫叫惊。
final修飾參數(shù)和變量
如果參數(shù)用final修飾款青,那么在方法中我們不能對這個final參數(shù)進行修改:
public void test(final int x) {
// x++; // 這句是非法的,因為x是final的
}
final修飾的變量(包括實例變量和類變量)具有引用不可變性霍狰,例如:private final int x = 1
抡草,這里變量x是final類型的,如果我們嘗試修改x:x = 2
蔗坯,編譯器就會提示出錯康震。
同樣的對于包裝類,如果我們嘗試修改變量的指針宾濒,一樣會提示錯誤:
class Test {
private int x;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
}
final Test test = new Test();
// test = new Test(); // 這個表達式是非法的腿短,因為test是final的
test.setX(2); // 這個表達式是合法的因為這個表達式?jīng)]有修改test的引用
注意這里包裝類的不可變性是指引用的不可變,如果我們不修改變量的引用绘梦,而是通過訪問變量所指向的包裝類的方法去修改包裝類的屬性橘忱,這個是合法的。
編譯器對final變量的優(yōu)化
請看下面的例子:
public class Test {
public static void main(String[] args) {
String a = "helloworld1";
final String b = "helloworld";
String c = "helloworld";
String d = b + 2;
String e = d + 2;
System.out.println((a == d));
System.out.println((a == e));
}
}
輸出結果:
true
false
可以看到a和d是指向同一個地址卸奉,而a和e則是指向不同的地址钝诚。這里就可以看出final變量和普通變量的區(qū)別了。變量b是final類型的榄棵,編譯器知道b的引用不會改變凝颇,因此直接可以“算出來”d就是“helloworld2”潘拱,那么a和d都是指向"helloworld2"字面量的變量,自然a == d
成立了拧略。編譯器知道一個字符串變量是final不可變的變量后芦岂,就可以直接進行替換了。對于編譯期間不能確定的final變量辑鲤,編譯器則不會進行替換盔腔,請看下面這個例子:
public class Test {
public static void main(String[] args) {
String a = "helloworld2";
final String b = getHelloWorld();
String c = b + 2;
System.out.println((a == c));
}
public static String getHelloWorld() {
return "helloworld";
}
}
這段代碼的輸出結果為false。
因為編譯器無法在編譯期就能確定b為"helloworld"月褥,因此編譯器無法對b進行替換弛随,所以a和c是不等的。
final的內(nèi)存語義
final域的重排序規(guī)則
- 在構造函數(shù)內(nèi)對一個final域的寫入宁赤,與隨后把這個被構造對象的引用賦值給一個引用變量舀透,這兩個操作之間不能重排序。
- 初次讀一個包含final域的對象的引用决左,與隨后初次讀這個final域愕够,這兩個操作之間不能重排序。
寫final域的重排序規(guī)則
寫final域的重排序規(guī)則禁止把final域的寫重排序到構造函數(shù)之外佛猛。這個規(guī)則的實現(xiàn)包含下面2個方面:
- JMM禁止編譯器把final域的寫重排序到構造函數(shù)之外惑芭。
- 編譯器會在final域的寫之后,構造函數(shù)return之前继找,插入一個StoreStore屏障遂跟。這個屏障禁止處理器把final域的寫重排序到構造函數(shù)之外。寫final域的重排序規(guī)則可以確保:在對象引用為任意線程可見之前婴渡,對象的final域已經(jīng)被正確初始化過了幻锁,而普通域不具有這個保障。
讀final域的重排序規(guī)則
讀final域的重排序規(guī)則是边臼,在一個線程中哄尔,初次讀對象引用與初次讀該對象包含的final域,JMM禁止處理器重排序這兩個操作(注意柠并,這個規(guī)則僅僅針對處理器)岭接。編譯器會在讀final域操作的前面插入一個LoadLoad屏障。初次讀對象引用與初次讀該對象包含的final域臼予,這兩個操作之間存在間接依賴關系亿傅。由于編譯器遵守間接依賴關系,因此編譯器不會重排序這兩個操作瘟栖。大多數(shù)處理器也會遵守間接依賴,也不會重排序這兩個操作谅阿。但有少數(shù)處理器允許對存在間接依賴關系的操作做重排序(比如alpha處理器)半哟,這個規(guī)則就是專門用來針對這種處理器的酬滤。讀final域的重排序規(guī)則可以確保:在讀一個對象的final域之前,一定會先讀包含這個final域的對象的引用寓涨。
final域為引用類型
對于引用類型盯串,寫final域的重排序規(guī)則對編譯器和處理器增加了如下約束:在構造函數(shù)內(nèi)對一個final引用的對象的成員域的寫入,與隨后在構造函數(shù)外把這個被構造對象的引用賦值給一個引用變量戒良,這兩個操作之間不能重排序体捏。這一規(guī)則確保了其他線程能讀到被正確初始化的final引用對象的成員域。