final 是Java 中重要關鍵字之一,可以應用於類、方法以及變數上。這篇文章中將講解什麼是 final 關鍵字?將變數、方法和類聲明為 final 代表了什麼?使用 final 的好處是什麼? final 關鍵字是什麼? final 在 Java 中是一個保留的關鍵字,可以聲明成員變數、方法、類 ...
final 是Java 中重要關鍵字之一,可以應用於類、方法以及變數上。這篇文章中將講解什麼是 final 關鍵字?將變數、方法和類聲明為 final 代表了什麼?使用 final 的好處是什麼?
final 關鍵字是什麼?
final 在 Java 中是一個保留的關鍵字,可以聲明成員變數、方法、類以及本地變數。一旦你將引用聲明作 final,你將不能改變這個引用了,編譯器會檢查代碼,如果試圖將變數再次初始化的話,編譯器會報編譯錯誤。
final 變數
凡是對成員變數或者本地變數(在方法中的或者代碼塊中的變數稱為本地變數)聲明為 final 的都叫作 final 變數。final 變數經常和 static 關鍵字一起使用,作為常量。下麵是 final 變數的例子:
public static final String NAME = "wupx";
NAME = new String("wupx"); //invalid compilation error
final 變數是只讀的。
final 方法
final 也可以聲明方法,Java 里用 final 修飾符去修飾一個方法的唯一正確用途就是表達:這個方法原本是一個虛方法,現在通過 final 來聲明這個方法不允許在派生類中進一步被覆寫(override)。
Java 中非私有的成員方法預設都是虛方法,而虛方法就可以在派生類中被覆寫。為了保證某個類上的某個虛方法不在派生類中被進一步覆寫,就需要使用 final 修飾符來聲明,讓編譯器(例如 javac)與 JVM 共同檢查並保證這個限制總是成立。
下麵引用 R 大 在知乎上的回答來打破“用 final 修飾方法可以讓對這個方法的調用變快”的流言:
曾經有一種廣為流傳的說法是用final修飾方法可以讓對這個方法的調用變快。這種說法在現代主流的優化JVM上都是不成立的(例如Oracle JDK / OpenJDK HotSpot VM、IBM J9 VM、Azul Systems Zing VM等)。這是因為:能用final修飾的虛方法,其派生類中必然不可能存在對其覆寫的版本,於是可以判定這個虛方法只有一個可能的調用目標;而如果此時把這個final修飾符去掉,這些先進的JVM都可以通過“類層次分析”(Class Hierarchy Analysis,CHA)來發現這個方法在派生類中沒有進一步覆寫的版本,於是同樣可以判定這個虛方法只有一個可能的調用目標。然後兩者的優化程度會一模一樣,無論是從“不需要通過虛分派而可以直接調用目標”(稱為“去虛化”,devirtualization)還是從“便於內聯”的角度看,這兩種情況都是一樣的。
而如果某個類層次結構中原本某個虛方法就存在多個覆寫版本的話,那麼本來也不可能對這個虛方法加上final修飾,所以就算這種情況下去虛化變得困難,鍋也不能讓“因為沒用final修飾”來背。
使用final關鍵字在現代主流的優化JVM上不會提升性能。
下麵是 final 方法的例子:
class User{
public final String getName(){
return "user:wupx";
}
}
class Reader extends User{
@Override
public final String getName(){
return "reader wupx"; //compilation error: overridden method is final
}
}
final 類
使用 final 來修飾的類叫作 final 類,final類通常功能是完整的,它們不能被繼承,Java 中有許多類是 final 的,比如 String, Interger 以及其他包裝類。下麵是 final 類的實例:
final class User{
}
class Reader extends User{ //compilation error: cannot inherit from final class
}
記憶體模型中的 final
對於 final 變數,編譯器和處理器都要遵守兩個重排序規則:
- 構造函數內,對一個 final 變數的寫入,與隨後把這個被構造對象的引用賦值給一個變數,這兩個操作之間不可重排序
- 首次讀一個包含 final 變數的對象,與隨後首次讀這個 final 變數,這兩個操作之間不可以重排序
實際上這兩個規則也正是針對 final 變數的寫與讀。寫的重排序規則可以保證,在對象引用對任意線程可見之前,對象的 final 變數已經正確初始化了,而普通變數則不具有這個保障;讀的重排序規則可以保證,在讀一個對象的 final 變數之前,一定會先讀這個對象的引用。如果讀取到的引用不為空,根據上面的寫規則,說明對象的 final 變數一定以及初始化完畢,從而可以讀到正確的變數值。
如果 final 變數的類型是引用型,那麼構造函數內,對一個 final 引用的對象的成員域的寫入,與隨後在構造函數外把這個被構造對象的引用賦值給一個引用變數,這兩個操作之間不能重排序。
實際上這也是為了保證 final 變數在對其他線程可見之前,能夠正確的初始化完成。
final 關鍵字的好處
下麵為使用 final 關鍵字的一些好處:
- final 關鍵字提高了性能,JVM 和 Java 應用都會緩存 final 變數
- final 變數可以安全的在多線程環境下進行共用,而不需要額外的同步開銷
總結
- final 關鍵字可以用於成員變數、本地變數、方法以及類
- final 成員變數必須在聲明的時候初始化或者在構造器中初始化,否則就彙報編譯錯誤
- 不能夠對 final 變數再次賦值
- 本地變數必須在聲明時賦值
- 在匿名類中所有變數都必須是 final 變數
- final 方法不能被重寫
- final 類不能被繼承
- 介面中聲明的所有變數本身是 final 的
- final 和 abstract 這兩個關鍵字是反相關的,final 類就不可能是 abstract 的
- 沒有在聲明時初始化 final 變數的稱為空白 final 變數(blank final variable),它們必須在構造器中初始化,或者調用 this() 初始化,不這麼做的話,編譯器會報錯final變數(變數名)需要進行初始化
- 按照 Java 代碼慣例,final 變數就是常量,而且通常常量名要大寫
- 對於集合對象聲明為 final 指的是引用不能被更改
參考
《Java編程思想》
https://www.zhihu.com/question/66083114