深入淺出的講解Java關鍵字final的作用
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的域變量還有內(nèi)存模型上的語義,下面詳細說
內(nèi)存模型的作用--防止變量從構造方法中逸出
這個主要是針對被final修飾的域變量,虛擬機會有禁止指令重排的保證:
- 在構造方法內(nèi)對一個final變量的寫入,與隨后這個被構造對象的引用賦值給一個引用變量,這二個順序不改變,final變量的寫入一定要早于對象引用的賦值。
什么意思呢?在多線程環(huán)境下,域變量是有可能從構造方法中逸出的,也就是說線程有可能讀到還沒有被構造方法初始化的域變量的值。比如:
class Foo { int a; Foo(int v) { a = v; } }
如果是在多線程環(huán)境下,一個線程A在創(chuàng)建Foo的對象,另一個線程B在讀對象的a的值,則B是有可能讀到未正確初始化a的值(默認初始值0)。這就是域變量從構造方法中逸出。
關鍵字final可以禁止虛擬機指令重排,從而保證了構造方法執(zhí)行完畢前final修飾的變量一定是初始化過了的。
匿名內(nèi)部類使用外部變量時為何要強制使用final修飾
這個大家肯定都習以為常了,比如
private void initViews() { final int a = 3; // Compilation error if remove final btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (a > 1) { // volala } } } }
那么,有沒有想過為什么?而像其他支持完整閉包的語言如JavaScript,Python等,是沒有這等限制的。究其原因,是Java對閉包支持不夠完整,或者說它并不是像動態(tài)語言那樣的完整閉包。對于匿名內(nèi)部類來說,編譯器會創(chuàng)建一個命名類(OutClass$1之類的),然后把匿名類所在的能捕獲的變量,以構造參數(shù)的形式傳遞給內(nèi)部類使用,這樣一樣,外部的變量與內(nèi)部類看到的變量是不同的,雖然它們的值是相同的,因此,如果再允許外部修改這些變量,或者內(nèi)部類里面修改這些變量,都會造成數(shù)據(jù)的不一致性(因為它們是不同的變量),所以Java強制要求匿名內(nèi)部類訪問的外部變量要加上final來修飾。
對于其他語言,匿名內(nèi)部類,持有的是外部變量的一個包裝的引用(wrapper reference),這可以能看不懂,但是理解起來就是內(nèi)部類能直接訪問外部變量,外部與閉包內(nèi)部訪問的是同一個變量,因此外部修改了,內(nèi)部能看到變化,內(nèi)部修改了,外部也能看到變化。
一句話總結就是,Java內(nèi)部類與外部持有的是值相同的不同的變量;其他支持閉包的語言則持有的是相同的變量。
建議能使用final的地方就加上final修飾
最后來聊聊,啥時候應該用final呢?孤的建議(以及眾多大師的建議)就是能多用就多用,除非不能用final,否則就用。原因,有這么幾條:
域變量盡可能加上final
這個原因比較明確,前面也提到了,在多線程條件下,會有很大的優(yōu)勢。盡可能加上final來修飾域變量,甚至用Immutable Object,可以省去構造時的多線程同步。
多線程最大的麻煩是狀態(tài)同步,啥是狀態(tài)?其實就是共享數(shù)據(jù),域變量就是共享數(shù)據(jù),所以,如果共享數(shù)據(jù)都是不可變的(Immutable),那么自然就沒有了同步上的麻煩。
final類和方法能提升性能
正常的類和方法,虛擬機需要為了繼承和方法覆寫而做一次準備,如果加上了final,虛擬機知道它不會被繼承或者覆寫,那么就可以做一些優(yōu)化。雖然,這并不顯著,但是還是可以提升一些性能的。
final變量能提升可讀性
無論是域變量還是本地變量,加上了final修飾,程序的維護者就知道了,這個變量的值不會再改變,這無疑會大大增加可讀性。
以上就是深入淺出的講解Java關鍵字final的作用的詳細內(nèi)容,更多關于Java 關鍵字 final的資料請關注腳本之家其它相關文章!
相關文章
springdata jpa使用Example快速實現(xiàn)動態(tài)查詢功能
這篇文章主要介紹了springdata jpa使用Example快速實現(xiàn)動態(tài)查詢功能,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11java實現(xiàn)微信小程序加密數(shù)據(jù)解密算法
這篇文章主要為大家詳細介紹了java實現(xiàn)微信小程序加密數(shù)據(jù)解密算法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-09-09java調(diào)用中國天氣網(wǎng)api獲得天氣預報信息的方法
這篇文章主要介紹了java調(diào)用中國天氣網(wǎng)api獲得天氣預報信息的方法,可實現(xiàn)調(diào)用溫度、風力風向及近期天氣狀況等功能,具有一定參考借鑒價值,需要的朋友可以參考下2015-07-07Java 高并發(fā)三:Java內(nèi)存模型和線程安全詳解
本文主要介紹Java高并發(fā)內(nèi)存模型和線程安全的資料,這里整理詳細的資料及1.原子性 2.有序性 3.可見性 4.Happen-Before 5.線程安全的概念,有需要的小伙伴可以參考下2016-09-09