來搞點final?

1.final的簡介

        final可以修飾變量,方法和類,用於表示所修飾的內容一旦賦值之後就不會再被改變,比如String類就是一個final類型的類。即使能夠知道final具體的使用方法,我想對final在多線程中存在的重排序問題也很容易忽略,希望能夠一起做下探討。

2.final的具體使用場景        

        final能夠修飾變量,方法和類,也就是final使用範圍基本涵蓋了java每個地方,下面就分別以鎖修飾的位置:變量,方法和類分別來說一說。

        2.1 變量

        在java中變量,可以分爲成員變量以及方法局部變量。因此也是按照這種方式依次來說,以避免漏掉任何一個死角。

            2.1.1 final成員變量

            通常每個類中的成員變量可以分爲類變量(static修飾的變量)以及實例變量。針對這兩種類型的變量賦初值的時機是不同的,類變量可以在聲明變量的時候直接賦初值或者在靜態代碼塊中給類變量賦初值。而實例變量可以在聲明變量的時候給實例變量賦初值,在非靜態初始化塊中以及構造器中賦初值。類變量有兩個時機賦初值,而實例變量則可以有三個時機賦初值。當final變量未初始化時系統不會進行隱式初始化,會出現報錯。這樣說起來還是比較抽象,下面用具體的代碼來演示。

            看上面的圖片已經將每種情況整理出來了,這裏用截圖的方式也是覺得在IDE出現紅色出錯的標記更能清晰的說明情況。現在我們來將這幾種情況歸納整理一下:

            類變量:必須要在靜態初始化塊中指定初始值或者聲明該類變量時指定初始值,而且只能在這兩個地方之一進行指定;

            實例變量:必要要在非靜態初始化塊聲明該實例變量或者在構造器中指定初始值,而且只能在這三個地方進行指定。

            2.2.2 final局部變量

            final局部變量由程序員進行顯式初始化,如果final局部變量已經進行了初始化則後面就不能再次進行更改,如果final變量未進行初始化,可以進行賦值,當且僅有一次賦值,一旦賦值之後再次賦值就會出錯。下面用具體的代碼演示final局部變量的情況。


            現在我們來換一個角度進行考慮,final修飾的是基本數據類型和引用類型有區別嗎?

            final基本數據類型 VS final引用數據類型

            通過上面的例子我們已經看出來,如果final修飾的是一個基本數據類型的數據,一旦賦值後就不能再次更改,那麼,如果final是引用數據類型了?這個引用的對象能夠改變嗎?我們同樣來看一段代碼。

            當我們對final修飾的引用數據類型變量person的屬性改成22,是可以成功操作的。通過這個實驗我們就可以看出來當final修飾基本數據類型變量時,不能對基本數據類型變量重新賦值,因此基本數據類型變量不能被改變。而對於引用類型變量而言,它僅僅保存的是一個引用,final只保證這個引用類型變量所引用的地址不會發生改變,即一直引用這個對象,但這個對象屬性是可以改變的。

            宏變量

            利用final變量的不可更改性,在滿足一下三個條件時,該變量就會成爲一個“宏變量”,即是一個常量。

            使用final修飾符修飾;

            1.在定義該final變量時就指定了初始值;

            2.該初始值在編譯時就能夠唯一指定。

            3.注意:當程序中其他地方使用該宏變量的地方,編譯器會直接替換成該變量的值

    2.2 方法

        重寫?

            當父類的方法被final修飾的時候,子類不能重寫父類的該方法,比如在Object中,getClass()方法就是final的,我們就不能重寫該方法,但是hashCode()方法就不是被final所修飾的,我們就可以重寫hashCode()方法。我們還是來寫一個例子來加深一下理解: 

                先定義一個父類,裏面有final修飾的方法test();

            然後FinalExample繼承該父類,當重寫test()方法時出現報錯,如下圖:

            通過這個現象我們就可以看出來被final修飾的方法不能夠被子類所重寫

        重載!!!

            可以看出被final修飾的方法是可以重載的。經過我們的分析可以得出如下結論:

            1. 父類的final方法是不能夠被子類重寫的

            2. final方法是可以被重載的

    2.3 類

        當一個類被final修飾時,表名該類是不能被子類繼承的。子類繼承往往可以重寫父類的方法和改變父類屬性,會帶來一定的安全隱患,因此,當一個類不希望被繼承時就可以使用final修飾。還是來寫一個小例子:

3.final的例子

        final經常會被用作不變類上,利用final的不可更改性。我們先來看看什麼是不變類。

        不變類

                不變類的意思是創建該類的實例後,該實例的實例變量是不可改變的。滿足以下條件則可以成爲不可變類:

                1.使用private和final修飾符來修飾該類的成員變量

                2.提供帶參的構造器用於初始化類的成員變量;

                3.僅爲該類的成員變量提供getter方法,不提供setter方法,因爲普通方法無法修改fina修飾的成員變量;

                4.如果有必要就重寫Object類 的hashCode()和equals()方法,應該保證用equals()判斷相同的兩個對象其Hashcode值也是相等的。

            JDK中提供的八個包裝類和String類都是不可變類,我們來看看String的實現。

            可以看出String的value就是final修飾的,上述其他幾條性質也是吻合的。

4. 多線程中你真的瞭解final嗎?

            上面我們聊的final使用,應該屬於Java基礎層面的,當理解這些後我們就真的算是掌握了final嗎?有考慮過final在多線程併發的情況嗎?在java內存模型中我們知道java內存模型爲了能讓處理器和編譯器底層發揮他們的最大優勢,對底層的約束就很少,也就是說針對底層來說java內存模型就是一弱內存數據模型。同時,處理器和編譯爲了性能優化會對指令序列有編譯器和處理器重排序。那麼,在多線程情況下,final會進行怎樣的重排序?會導致線程安全的問題嗎?下面,就來看看final的重排序。

        PS:暫時這樣 最近沒時間寫 結果東西一多 好多自己還沒明白,抱歉了各位大佬們。

        4.1 final域重排序規則

            4.1.1 final域爲基本類型

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章