final是Java中的一個關鍵字,final可用於修飾類、方法、參數和變量(包括實例變量和類變量)。bash
final修飾的類具備不可繼承性,也就是若是一個類是final類型的,則這個類不容許有子類。首先咱們頂一個final類:ide
public final class FinalClass {
private int field;
}
複製代碼
而後若是咱們嘗試去繼承這個類的話編譯器會報錯:函數
public class FalseExtension extends FinalClass {
}
複製代碼
final修飾的方法具備不可變性,也就是說final的方法不容許在子類中被覆寫(@Override)。下面咱們來看一個反例:優化
若是參數用final修飾,那麼在方法中咱們不能對這個final參數進行修改:ui
public void test(final int x) {
// x++; // 這句是非法的,由於x是final的
}
複製代碼
final修飾的變量(包括實例變量和類變量)具備引用不可變性,例如:private final int x = 1
,這裏變量x是final類型的,若是咱們嘗試修改x:x = 2
,編譯器就會提示出錯。 一樣的對於包裝類,若是咱們嘗試修改變量的指針,同樣會提示錯誤:this
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); // 這個表達式是合法的由於這個表達式沒有修改test的引用
複製代碼
注意這裏包裝類的不可變性是指引用的不可變,若是咱們不修改變量的引用,而是經過訪問變量所指向的包裝類的方法去修改包裝類的屬性,這個是合法的。spa
請看下面的例子:線程
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變量和普通變量的區別了。變量b是final類型的,編譯器知道b的引用不會改變,所以直接能夠「算出來」d就是「helloworld2」,那麼a和d都是指向"helloworld2"字面量的變量,天然a == d
成立了。編譯器知道一個字符串變量是final不可變的變量後,就能夠直接進行替換了。對於編譯期間不能肯定的final變量,編譯器則不會進行替換,請看下面這個例子:code
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域的重排序規則禁止把final域的寫重排序到構造函數以外。這個規則的實現包含下面2個方面:
讀final域的重排序規則是,在一個線程中,初次讀對象引用與初次讀該對象包含的final域,JMM禁止處理器重排序這兩個操做(注意,這個規則僅僅針對處理器)。編譯器會在讀final域操做的前面插入一個LoadLoad屏障。初次讀對象引用與初次讀該對象包含的final域,這兩個操做之間存在間接依賴關係。因爲編譯器遵照間接依賴關係,所以編譯器不會重排序這兩個操做。大多數處理器也會遵照間接依賴,也不會重排序這兩個操做。但有少數處理器容許對存在間接依賴關係的操做作重排序(好比alpha處理器),這個規則就是專門用來針對這種處理器的。讀final域的重排序規則能夠確保:在讀一個對象的final域以前,必定會先讀包含這個final域的對象的引用。
對於引用類型,寫final域的重排序規則對編譯器和處理器增長了以下約束:在構造函數內對一個final引用的對象的成員域的寫入,與隨後在構造函數外把這個被構造對象的引用賦值給一個引用變量,這兩個操做之間不能重排序。這一規則確保了其餘線程能讀到被正確初始化的final引用對象的成員域。