final 要害字經(jīng)常被誤用 - 聲明類和方法時使用過度,而聲明實例字段時卻使用不足。本月,java 實踐者 Brian Goetz 探究了一些有關(guān)有效使用 final 的準(zhǔn)則。 如同它的“表親”- C 中的 const 要害字一樣,根據(jù)上下文,final 表示不同的東西。final 要害字可應(yīng)用于類、方法或字段。應(yīng)用于類時,意味著該類不能再生成子類。應(yīng)用于方法時,意味著該方法不能被子類覆蓋。應(yīng)用于字段時,意味著該字段的值在每個構(gòu)造器內(nèi)必須只能賦值一次而且此后該值永遠(yuǎn)不變。
為什么這個類是 final? 對于開發(fā)人員來說,將類聲明為 final,卻不給出為何作出這一決定的說明,這樣的做法很普遍,在開放源碼項目中尤其如此。一段時間之后,非凡是假如原來的開發(fā)人員不再參與代碼的維護(hù),其它開發(fā)人員將總是發(fā)問“為何類 X 被聲明成 final?”。通常沒人知道,當(dāng)有人確實知道或喜歡猜測時,答案幾乎總是“因為這能使它運行得更快”。普遍的理解是:將類或方法聲明成 final 會使編譯器更輕易地內(nèi)聯(lián)方法調(diào)用,但是這種理解是不正確的(或者至少說是大大地言過其實了)。
final 類和方法在編程時可能是非常大的麻煩 - 它們限制您選擇重用已有的代碼和擴(kuò)展已有類的功能。有時有很好的理由將類聲明成 final(如強制不變性),此時使用 final 的益處將大于其不便之處。性能提高幾乎總是成為破壞良好的面向?qū)ο笤O(shè)計原則的壞理由,而當(dāng)性能提高很小或者根本沒有提高時,則它真正是個很差的權(quán)衡方法。
過早優(yōu)化 出于性能的考慮,在項目的早期階段將方法或類聲明成 final 是個壞主意,這有多個原因。首先,早期階段設(shè)計不是考慮循環(huán)計算性能優(yōu)化的時候,尤其當(dāng)此類決定可能約束您使用 final 進(jìn)行設(shè)計。其次,通過將方法或類聲明成 final 而獲得的性能優(yōu)勢通常為零。而且,將復(fù)雜的有狀態(tài)的類聲明成 final 不利于面向?qū)ο蟮脑O(shè)計,并導(dǎo)致體積龐大且面面俱到的類,因為它們不能輕松地重構(gòu)成更小更緊湊的類。
和許多有關(guān) Java 性能的神話一樣,將類或方法聲明成 final 會帶來更佳的性能,這一錯誤觀念被廣泛接受但極少進(jìn)行檢驗。其論點是:將方法或類聲明成 final 意味著編譯器可以更加積極地內(nèi)聯(lián)方法調(diào)用,因為它知道在運行時這正是要調(diào)用的方法的版本。但這顯然是不正確的。僅僅因為類 X 編譯成 final 類 Y,并不意味著同樣版本的類 Y 將在運行時被裝入。因此編譯器不能安全地內(nèi)聯(lián)這樣的跨類方法調(diào)用,不管是不是 final。只有當(dāng)方法是 PRivate 時,編譯器才能自由地內(nèi)聯(lián)它,在這種情況下,final 要害字是多余的。
另一方面,運行時環(huán)境和 JIT 編譯器擁有更多有關(guān)真正裝入什么類的信息,可以比編譯者作出好得多的優(yōu)化決定。假如運行時環(huán)境知道沒有裝入繼續(xù) Y 的類,那么它可以安全地內(nèi)聯(lián)對 Y 方法的調(diào)用,不管 Y 是不是 final(只要它能在隨后裝入 Y 子類時使這種 JIT 編譯的代碼無效)。因此事實是,盡管 final 對于不執(zhí)行任何全局相關(guān)性分析的“啞”運行時優(yōu)化器可能是有用的,但它的使用實際上不支持太多的編譯時優(yōu)化,而且智能的 JIT 執(zhí)行運行時優(yōu)化時不需要它。
似曾相識 - 重新回憶 register 要害字 final 用于優(yōu)化決定時和 C 中不贊成使用的 register 要害字非常相似。讓程序員幫助優(yōu)化器這一愿望促成了 register 要害字,但事實上,發(fā)現(xiàn)這并不是很有用。正如我們在其它方面愿意相信的那樣,在作出代碼優(yōu)化決定方面編譯器通常比人做得出色,在現(xiàn)在的 RISC 處理器上更是如此。事實上,大多數(shù) C 編譯器完全忽略了 register 要害字。早先的 C 編譯器忽略它是因為這些編譯器根本就不起優(yōu)化作用;現(xiàn)今的編譯器忽略它是因為編譯器不用它就能作更好的優(yōu)化決定。任何一種情況下,register 要害字都沒有添加什么性能優(yōu)勢,和應(yīng)用于 Java 類或方法的 final 要害字很相似。假如您想優(yōu)化您的代碼,請堅持使用那些可以大幅度提高性能的優(yōu)化,比如使用有效的算法且不執(zhí)行冗余的計算 - 將循環(huán)計算優(yōu)化留給編譯器和 JVM 去做。
使用 final 保持不變性 雖然性能并不是將類或方法聲明為 final 的好理由,然而有時仍有充足的理由編寫 final 類。最常見的是 final 保證那些旨在不發(fā)生變化的類保持不變。不變類對于簡化面向?qū)ο蟪绦虻脑O(shè)計非常有用 - 不變的對象只需要較少的防御性編碼,并且不要求嚴(yán)格的同步。您不會在您的代碼中構(gòu)建這一設(shè)想:類是不變的,然后讓某些人用使其可變的方式來繼續(xù)它。將不變的類聲明成 final 保證了這類錯誤不會偷偷溜進(jìn)您的程序中。
final 用于類或方法的另一個原因是為了防止方法間的鏈接被破壞。例如,假定類 X 的某個方法的實現(xiàn)假設(shè)了方法 M 將以某種方式工作。將 X 或 M 聲明成 final 將防止派生類以這種方式重新定義 M,從而導(dǎo)致 X 的工作不正常。盡管不用這些內(nèi)部相關(guān)性來實現(xiàn) X 可能會更好,但這不總是可行的,而且使用 final 可以防止今后這類不兼容的更改。
假如您必須使用 final 類或方法,請記錄下為什么這么做 無論何種情況,當(dāng)您確實選擇了將方法或類聲明成 final 時,請記錄下為什么這樣做的原因。否則,今后的維護(hù)人員將可能迷惑這樣做是否有好的原因(因為經(jīng)常沒有);而且會被您的決定所約束,但同時還不知道您這樣做的動機(jī)是為了得到什么好處。在許多情況下,將類或方法聲明成 final 的決定一直推遲到開發(fā)過程后期是有意義的,那時您已經(jīng)擁有關(guān)于類是如何交互及可能如何被繼續(xù)的更好信息了。您可能發(fā)現(xiàn)您根本不需要將類聲明為 final,或者您可以重構(gòu)類以便將 final 應(yīng)用于更小更簡單的類。