1. 程式人生 > >深入理解final關鍵字以及一些建議

深入理解final關鍵字以及一些建議

重新 -i 想是 .class tro orm print 靜態工廠方法 給他

引子:一說到final關鍵字,相信大家都會立刻想起一些基本的作用,那麽我們先稍微用寥寥數行來回顧一下。

一、final關鍵字的含義

final是Java中的一個保留關鍵字,它可以標記在成員變量、方法、類以及本地變量上。一旦我們將某個對象聲明為了final的,那麽我們將不能再改變這個對象的引用了。如果我們嘗試將被修飾為final的對象重新賦值,編譯器就會報錯。

二、用法

1.修飾變量

final修飾在成員變量或者局部變量上,那麽我們可以稱這個變量是final變量,這可能使我們用到最多的地方,舉個栗子:常量(雖然現在建議使用枚舉類來代替常量)。

技術分享圖片

如果我們將被final修飾的變量重新賦值,編譯器就會報出如圖:cannot assign a value to final variable.(不能給final變量賦值)

2.修飾方法

被final所修飾的方法將無法被子類重寫。

使用final方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修改它的含義;第二個原因是效率。在早期的Java實現版本中,會將final方法轉為內嵌調用。但是如果方法過於龐大,可能看不到內嵌調用帶來的任何性能提升。在最近的Java版本中,不需要使用final方法進行這些優化了。” -- 摘自《Java編程思想》

因此如果你認為一個方法的功能已經足夠完整了,子類中不需要改變的話,你可以聲明此方法為final。final方法比非final方法要快,因為在編譯的時候已經靜態綁定了,不需要在運行時再動態綁定(正如編程思想中所提到的,在現在幾版較新的JDK中,已經幾乎沒有性能差別了)。

技術分享圖片

(當我們嘗試重寫的時候編譯器就會報錯)。

註:類的private方法會隱式地被指定為final方法。

3.修飾類

如果某個類被final所修飾,那麽表明這個的功能通常是完整的;該類將不能被繼承。並且final類的所有方法都會被隱式的修飾成final

4.ps:匿名類中的所有變量都必須是final的。

三、關鍵字final的好處小結

  1. final關鍵字提高了性能。JVM和Java應用都會緩存final變量。
  2. final變量可以安全的在多線程環境下進行共享,而不需要額外的同步開銷。
  3. 使用final關鍵字,JVM會對方法、變量及類進行優化。
  4. 對於不可變類,它的對象是只讀的,可以在多線程環境下安全的共享,不用額外的同步開銷。

四、來自《Effective Java》中的一些建議

該書的第17條:要麽為了繼承而設計,並提供文檔說明,要麽就禁止繼承

該條目提醒我們,如果類不是被設計用來繼承的,那麽這個類就應該被禁止繼承(聽起來有點繞,但細想下來的設計思想是很好的),否則就應該提供足夠的文檔及註釋(具體可參考java.util.AbstractCollection這個骨架實現裏的註釋文檔規範)。

而禁止類被子類化的方法通常有兩個:

1.將所有的構造器設為私有的(private)或者包級私有的(default),並使用靜態工廠方法來代替構造器;

2.將類標記為final。

五、思考

1.一些思考回頭再來審視我們日常中的程序,我們可能已經習慣了不去那麽刻意的使用final,頂多在寫常量的時候用一用,但實際上我們很多的類,方法或者變量是不需要被改變的,或者說不會被繼承的。比如我在剛讀到《Effective Java》中的這個條目後,回首自己正在做的一個項目中審視了一下,我首先將自己的domain層中的一些類標為了final,因為我覺得這些類是不可能被繼承的,如果繼承了是不太符合設計的,並且程序運行沒有異常,同時修改的還有我的依賴註入方式(參考我的上一篇博客:Spring註解依賴註入的三種方式的優缺點以及優先選擇)

我重新糾正了一下自己在設計類的時候的思想順序:之前自己在準備寫一個類的時候(雖然通常我是不給類加final的= =),可能覺得這個類(變量或者方法)不能被改變,有很強烈的這種想法時才會加上final,但現在是:這個類需不需要使他可以被子類化?如果在以後的項目更新,叠代中,並不需要,那麽我會毫不猶豫的給他加上final。

2."final關鍵字能提升性能"?

當時發現這一點之後,我可能是中毒了,給能加上final的地方都加上了,自以為改善了性能心裏還美滋滋呢。其實對這個“提升性能”一點一直還有一絲的疑問,於是我回頭就去了Stack Overflow上轉了一圈,找到了我想要的答案:Does use of final keyword in Java improve the performance?

大佬指出,通常是不會的,對於方法,HotPot會跟蹤看它是否真的被重寫了,並且能夠優化沒有被重寫的內斂方法,直到它加載到了一個類復寫了這個方法,這時它可以撤銷(或部分撤銷)這些優化。(當然,這是假設您使用的是HotPot,但到目前為止這是最常見的JVM,所以…)

之後大佬指出了我們不應該為了這麽絲許的性能而絞盡腦汁,建議我們應該明確設計,寫出好的結構的代碼以及可讀性優良的代碼。(在此又應證了《Effective Java》中的第55條:謹慎地進行優化中所指出的核心:優化的格言就是:不要進行優化) (也驗證了上面《Java編程思想》中最後的那句話)

3.關於局部變量以及參數中的final

接著我嘗試將我的局部變量以及方法中的參數都標記為final的,同2一樣,已經中毒頗深了。但是我對此同時也存在著同樣的疑問,然後在Stack Overflow中得到了經驗證的又一個結論:局部變量以及參數中的final,同樣不能提升我們的性能,它甚至不會被寫進字節碼中。於是我操起了鍵盤啪啪啪一頓敲了幾行代碼編譯了一下,並用反編譯工具(如JD-GUI)打開:

先來看我們的源碼:

public class FinalTest {
    private static void say(final int number) {
        System.out.println("number: " + number);
    }

    public static void main(String[] args) {
        final int num = 0;
        say(num);
    }
}

再來看看編譯後的.class文件:

public class FinalTest {
    public FinalTest() {
    }

    private static void say(int number) {
        System.out.println("number: " + number);
    }

    public static void main(String[] args) {
        int num = false;
        say(0);
    }
}

可以看到在寫入字節碼的時候就被優化掉了,final只是編譯時靜態限制我們不能再賦值(改變引用)。

深入理解final關鍵字以及一些建議