final可以用來幹什麼
final是Java中非常常見的一個關鍵字,可以說每天都在使用它,雖然常見,但卻也不見得都那麼顯而易見,今天就來研究一下final,以加深對它的了解和更合理的運用。
修飾類
當一個類不想被繼承時,就可以用final來修飾。
修飾方法
當一個方法不想被子類覆寫(Override)時,可以用final來修飾。另外一方面,把方法用final來修飾也有一定的性能提升上的幫助,因為虛拟機知道它不會被覆寫,是以可以以更簡單的方式來處理。
private的方法,預設都會被編譯器加上final.
修飾變量
被final修飾的變量隻能指派一次,之後不能再被修改。如:
final int a = 10;a = 4; // compilation error
需要注意的是,這裡說的是隻能指派一次,并不意味着,非要在聲明變量時直接初始化,比如,下面的代碼也是完全合法的:
final int a;if (foo()) {a = 3;} else {a = 4;}
修飾域變量
域變量也是變量,是以用final來修飾的第一個作用就是指派後,不能再修改變量的值,比如:
final int a = 10;final Object b = new Object();
對于基本類型來說,就是變量值不能再被修改;對于引用來說,就是不能再讓其指向其他對象或者null。
但對于域變量,聲明為final的域變量必須在聲明時初始化,或者在構造方法中初始化,否則會有編譯錯誤。
此外,聲明為final的域變量還有記憶體模型上的語義,下面詳細說
記憶體模型的作用–防止變量從構造方法中逸出
這個主要是針對被final修飾的域變量,虛拟機會有禁止指令重排的保證:
在構造方法内對一個final變量的寫入,與随後這個被構造對象的引用指派給一個引用變量,這二個順序不改變,final變量的寫入一定要早于對象引用的指派。
什麼意思呢?在多線程環境下,域變量是有可能從構造方法中逸出的,也就是說線程有可能讀到還沒有被構造方法初始化的域變量的值。比如:
class Foo {int a;Foo(int v) {a = v;}}
如果是在多線程環境下,一個線程A在建立Foo的對象,另一個線程B在讀對象的a的值,則B是有可能讀到未正确初始化a的值(預設初始值0)。這就是域變量從構造方法中逸出。
關鍵字final可以禁止虛拟機指令重排,進而保證了構造方法執行完畢前final修飾的變量一定是初始化過了的。
匿名内部類使用外部變量時為何要強制使用final修飾
這個大家肯定都習以為常了,比如:
private void initViews() {final int a = 3; // Compilation error if remove finalbtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (a > 1) {// volala}}}}
那麼,有沒有想過為什麼?而像其他支援完整閉包的語言如JavaScript,Python等,是沒有這等限制的。究其原因,是Java對閉包支援不夠完整,或者說它并不是像動态語言那樣的完整閉包。對于匿名内部類來說,編譯器會建立一個命名類(OutClass$1之類的),然後把匿名類所在的能捕獲的變量),以構造參數的形式傳遞給内部類使用,這樣一樣,外部的變量與内部類看到的變量是不同的,雖然它們的值是相同的,是以,如果再允許外部修改這些變量,或者内部類裡面修改這些變量,都會造成資料的不一緻性(因為它們是不同的變量),是以Java強制要求匿名内部類通路的外部變量要加上final來修飾。
對于其他語言,匿名内部類,持有的是外部變量的一個包裝的引用(wrapper reference),這可以能看不懂,但是了解起來就是内部類能直接通路外部變量,外部與閉包内部通路的是同一個變量,是以外部修改了,内部能看到變化,内部修改了,外部也能看到變化。
一句話總結就是,Java内部類與外部持有的是值相同的不同的變量;其他支援閉包的語言則持有的是相同的變量。
建議能使用final的地方就加上final修飾
最後來聊聊,啥時候應該用final呢?孤的建議(以及衆多大師的建議)就是能多用就多用,除非不能用final,否則就用。原因,有這麼幾條:
域變量盡可能加上final
這個原因比較明确,前面也提到了,在多線程條件下,會有很大的優勢。盡可能加上final來修飾域變量,甚至用Immutable Object,可以省去構造時的多線程同步。
多線程最大的麻煩是狀态同步,啥是狀态?其實就是共享資料,域變量就是共享資料,是以,如果共享資料都是不可變的(Immutable),那麼自然就沒有了同步上的麻煩。
final類和方法能提升性能
正常的類和方法,虛拟機需要為了繼承和方法覆寫而做一次準備,如果加上了final,虛拟機知道它不會被繼承或者覆寫,那麼就可以做一些優化。雖然,這并不顯著,但是還是可以提升一些性能的。
final變量能提升可讀性
無論是域變量還是本地變量,加上了final修飾,程式的維護者就知道了,這個變量的值不會再改變,這無疑會大大增加可讀性。
參考資料