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