Menu
快讀
  • 旅遊
  • 生活
    • 美食
    • 寵物
    • 養生
    • 親子
  • 娛樂
    • 動漫
  • 時尚
  • 社會
  • 探索
  • 故事
  • 科技
  • 軍事
  • 国际
快讀

effective java(一)

2021 年 3 月 11 日 晓峰全财经

第 1 條:用靜態工廠方法替代構造器

優點:靜態工廠它們有名稱。 如果構造器的參數本身沒有確切地描述正被返回的對象,那麽具有適當名稱的靜態工廠會更容易使用,産生的客戶端代碼也更易于閱讀;不必在每次調用它們的時候都創建一個新對象。不可變類可以使用預先構建好的實例,或者將構建好的實例緩存起來,進行重複利用,從而避免創建不必要的重複對象;靜態工廠可以返回原返回類型的任何子類型的對象。這樣我們在選擇返回對象的類時就有了更大的靈活性;所返回的對象的類可以隨著每次調用而發生變化,這取決于靜態工廠方法的參數值;方法返回的對象所屬的類,在編寫包含該靜態工廠方法的類時可以不存在

缺點:靜態工廠方法的主要缺點在子,類如果不含公有的或者受保護的構造器,就不能被子類化 。 例如,要想將 Collections Framework 中的任何便利的實現類子類化 , 這是不可能的 。但是這樣也許會因禍得福,因爲它鼓勵程序員使用複合( composition ),而不是繼承,這正是不可變類型所需要的。靜態工廠方法的第二個缺點在于,程序員很難發現它們 。

第 2 條:遇到多個構造器參數時要考慮使用構建器

重疊構造器模式可行,但是當有許多參數的時候,客戶端代碼會很難縮寫,並且仍然較難以閱讀。構建器不直接生成想要的對象,而是讓客戶端利用所有必要的參數調用構造器(或者靜態工廠),得到一個 builder 對象。然後客戶端在 builder 對象上調用類似于 setter 的方法,來設置每個相關的可選參數 。 最後客戶端調用無參的 build 方法來生成通常是不可變的對象。

爲了創建對象 ,必須先創建它 的構建器 。 雖然創建這個構建器的開銷在實踐中可能不那麽明顯 但是在某些十分注重性能的情況下,可能就成問題了 。 Builder 模式比重疊構造器模式更加冗長 ,因此它只在有很多參數的時候才使用,比如4個或者更多個參數 。 但是記住,將來你可能需要添加參數。 如果一開始就使用構造器或者靜態工廠,等到類需要多個參數時才添加構造器,就會無法控制,那些過時的構造器或者靜態工廠顯得十分不協調 。 因此,通常最好一開始就使用構建器 。簡而言之 , 如果類的構造器或者靜態工廠中具有多個參數,設計這種類時, Builder模式就是一種不錯的選擇, 特別是當大多數參數都是可選或者類型相同的時候 。 與使用重疊構造器模式相比,使用 Bui lder 模式的客戶端代碼將更易于閱讀和編寫,構建器也比JavaBeans 更加安全 。

第 3 條:用私有構造器或者枚舉類型強化 Singleton 屬性

單元素的枚舉類型經常成爲實現 Singleton 的最佳方法。注意,如果 Singleton必須擴展一個超類,而不是擴展 Enum的時候,則不宜使用這個方法。

第 4 條:通過私有構造器強化不可實例化的能力

工具類不希望被實例化,因爲實例化對它沒有任何意義 。然而,在缺少顯式構造器的情況下,編譯器會自動提供一個公有的、無參的缺省構造器( default constructor )。對于用戶而言,這個構造器與其他的構造器沒有任何區別。

第 5 條:優先考慮依賴注人來引用資源

不要用 Singleton 和靜態工具類來實現依賴一個或多個底層資源的類,且該資源的行爲會影響到該類的行爲 ;也不要直接用這個類來創建這些資源 。 而應該將這些資源或者工廠傳給構造器(或者靜態工廠,或者構建器),通過它們來創建類 。 這個實踐就被稱作依賴注入,它極大地提升了類的靈活性 、 可重用性和可測試性 。

第 6 條:避免創建不必要的對象

例如不必每次使用new String(“str”)或者在校驗正則表達式的時候不使用String的matches方法,因爲該方法每次都會new一個Pattern對象。

自動裝箱使得基本類型和裝箱基本類型之間的差別變得模糊起來,但是並沒有完全消除 。所以要優先使用基本類型而不是裝箱基本類型,要當心無意識的自動裝箱。

第 7 條:消除過期的對象引用

第 8 條:避免使用終結方法和清除方法

終結方法( finalize)通常是不可預測的,也是很危險的,一般情況下是不必要的 。 使用終結方法會導致行爲不穩定 、 性能降低,以及可移植性問題。 當然,終結方法也有其可用之處,我們將在本條目的最後再做介紹;但是根據經驗,應該避免使用終結方法 。 在 Java 9中用清除方法( cleaner)代替了終結方法 。 清除方法沒有終結方法那麽危險,但仍然是不可預測、運行緩慢,一般情況下也是不必要的 。

第 9 條: try-with-resources 優先于 try-finally

代碼將更加簡潔、清晰,産生的異常也更有價值,如果調用 readLine 和close方法都抛出異常,後一個異常就會被禁止,以保留第一個異常 。 事實上,爲了保留你想要看到的那個異常,即便多個異常都可以被禁止。這些被禁止的異常並不是簡單地被抛棄了,而是會被打印在堆棧軌迹中,並注明它們是被禁止的異常。

第 10 條:覆蓋 equals 時請遵守通用約定

自反性( reflexive ) : 對于任何非 null 的引用值 x x . equals(x )必須返回 true 。

對稱性( symmetric ):對于任何非 null 的引用值 x 和 y ,當且僅當 y.equals(x )返回true 時, x.equals(y )必須返回 true 。

傳遞性( transitive ):對于任何非 null 的引用值 x 、 y 和 z ,如果 x.equals(y )返回

true ,並且 y.equals(z )也返回 true ,那麽 x.equals(z )也必須返回 true 0

一致性( consistent ) : 對于任何非 nu ll 的 引用值 x 和 y ,只要 equals 的比較操作

在對象中所用的信息沒有被修改,多次調用 x.equals(y )就會一致地返回 true,或者一致地返回 false

1. 使用==操作符檢查“參數是否爲這個對象的引用” 。 如果是,則返回 true 。 這只不過是一種性能優化,如果比較操作有可能很昂貴,就值得這麽做。

2 . 使用 instanceof 操作符檢查“參數是否爲正確的類型” 。 如果不是,則返回 false ,一般說來,所謂“正確的類型”是指 equals 方法所在的那個類 。 某些情況下,是指該類所實現的某個接口 。 如果類實現的接口改進了equals 約定,允許在實現了該接口的類之間進行比較,那麽就使用接口 。 集合接口如 Set 、 List 、 Map 和 Map . Entry 具有這樣的特性 。

3. 把參數轉換成正確的類型 。 因爲轉換之前進行過且 instance of 測試,所以確保會成功 。

4 . 對于該類中的每個“關鍵”域,檢查參數中的域是否與該對象中對應的域相匹配 。 如果這些測試全部成功, 則返回 true ;否則返回 false 。 如果第 2 步中的類型是個接口,就必須通過接口方法訪問參數中的域;如果該類型是個類,也許就能夠直接訪問參數中的域,這要取決于它們的可訪問性 。

覆蓋 equals 時總要覆蓋 hashCode方法

不要企圖讓 equals 方法過于智能。如果只是簡單地測試域中的值是否相等,則不難做到遵守 equals 約定 。 如果想過度地去尋求各種等價關系, 則很容易陷入麻煩之中 。 把任何一種別名形式考慮到等價的範圍內,往往不會是個好主意 。

不要將equals聲明中的Object對象替換爲其他的類型。 問題在于,這個方法並沒有覆蓋Object. equals ,因爲它的參數應該是 Object 類型,相反,它重載( overload )了 Object.equals 。 在正常equals 方法的基礎上,再提供一個“強類型”的 equals 方法,這是無法接受的,因爲會導致子類中的 Override 注解産生錯誤的正值,帶來錯誤的安全感。

不要輕易覆蓋 equals 方法,除非迫不得已。因爲在許多情況下,從Object 處繼承的實現正是你想要 的 。 如果覆蓋 equals ,一定要比較這個類的所有關鍵域。

第 11 條:覆蓋 equals 時總要覆蓋 hashCode

相等的對象必須具有相等的散列碼(hash code),覆蓋equals不覆蓋hashCode,會導致程序將無法正確運行。 hashCode方法必須遵守Object規定的通用約定,並且必須完成一定的工作,將不相等的散列碼分配給不相等的實例。這個很容易實現,但是如果不想那麽費力,可以使用Objects的hash方法。

第 12 條:始終要覆蓋 toString

toString 方法應該返回對象中包含的所有值得關注的信息,

第 13 條 : 謹慎地覆蓋 clone

實際上, clone 方法就是另一個構造器;必須確保它不會傷害到原始的對象,並確保正確地創建被克隆對象中的約束條件。總之,複制功能最好由構造器或者工廠方法提供 。 這條規則最絕對的例外是數組,最好利用 clone 方法複制數組。

第 14 條:考慮實現 Comparable 接口

由 compare To 方法施加的等同性測試,也必須遵守相同于 equals 約定所施加的限制條件:自反性 、 對稱性和傳遞性

每當實現一個對排序敏感的類時,都應該讓這個類實現 Comparable接口,以便其實例可以輕松地被分類 、 搜索,以及用在基于比較的集合中 。 每當在 compareTo 方法的實現中比較域值時,都要避使用 < 和>操作符,而應該在裝箱基本類型的類中使用靜態的 compare 方法,或者在 Comparator 接口中使用比較器構造方法 。

第 15 條:使類和成員的可訪問性最小化

信息隱藏可以有效地解除組成系統的各組件之間 的藕合關系,即解耦,使得這些組件可以獨立地開發、 測試、優化 、使用、理解和修改 。 因爲這些組件可以並行開發,所以加快了系統開發的速度 。 同時減輕了維護的負擔,程序員可以更快地理解這些組件,並且在調試它們的時候不影響其他的組

件。 信息隱藏提高了軟件的可重用性,因爲組件之間並不緊密相連,除了開發這些模塊所使用的環境之外,它們在其他的環境中往往也很有用 。 最後,信息隱藏也降低了構建大型系統的風險,因爲即使整個系統不可用,這些獨立的組件仍有可能是可用的 。

如果方法覆蓋了超類中的一個方法,子類中的訪問級別就不允許低于超類中的訪問級別。這樣可以確保任何可使用超類的實例的地方也都可以使用子類的實例。

公有類的實例域決不能是公有的。 如果實例域是非final 的,或者是一個指向可變對象的 final 引用, 那麽一旦使這個域成爲公有的,就等于放棄了對存儲在這個域中的值進行限制的能力;這意味著,你也放棄了強制這個域不可變的能力 。 同時,當這個域被修改的時候,你也失去了對它采取任何行動的能力 。 因此, 包含公有可變域的類通常並不是線程安全的 。 即使域是 final 的,並且引用不可變的對象,但當把這個域變成公有的時候,也就放棄了“切換到一種新的內部數據表示法”的靈活性 。

第 16 條:要在公有類而非公有域中使用訪問方法

公有類永遠都不應該暴露可變的域。 雖然還是有問題,但是讓公有類暴露不可變的域,其危害相對來說比較小 。 但有時候會需要用包級私有的或者私有的嵌套類來暴露域,無論這個類是可變的還是不可變的 。

第 17 條:使可變性最小化

不可變類是指其實例不能被修改的類,例如String

爲了使類成爲不可變,要遵循下面五條規則:

1. 不要提供任何會修改對象狀態的方法 (也稱爲設值方法) 。

2. 保證類不會被擴展。這樣可以防止粗心或者惡意的子類假裝對象的狀態已經改變,從而破壞該類的不可變行爲 。 爲了防止子類化,一般做法是聲明這個類成爲 final。

3. 聲明所有的域都是final的 。通過系統的強制方式可以清楚地表明你的意圖 。 而且,如果一個指向新創建實例的引用在缺乏同步機制的情況下,從一個線程被傳遞到另一個線程,就必須確保正確的行爲。

4 . 聲明所有的域都爲私有的。這樣可以防止客戶端獲得訪問被域引用的可變對象的權限,井防止客戶端直接修改這些對象 。 允許不可變的類具有公有的final域,只要這些域包含基本類型的值或者指向不可變對象的引用,但是不建議這樣做,因爲這樣會使得在以後的版本中無法再改變內部的表示法。

5. 確保對子任何可變組件的互斥訪問 。 如果類具有指向可變對象的域,則必須確保該類的客戶端無法獲得指向這些對象的引用。並且,永遠不要用客戶端提供的對象引用來初始化這樣的域,也不要從任何訪問方法中返回該對象引用。 在構造器、訪問方法和 readObject 方法中使用保護性拷貝。

不可變對象(final Object)本質上是線程安全的,它們不要求同步 。 當多個線程並發訪問這樣的對象時它們不會遭到破壞 。 這無疑是獲得線程安全最容易的辦法 。 實際上,沒有任何線程會注意到其他線程對于不可變對象的影響。所以,不可變對象可以被自由地共享 。 不可變類應該充分利用這種優勢,鼓勵客戶端盡可能地重用現有的實例 。 要做到這一點,一個很簡便的辦法就是:對于頻繁用到的值,爲它們提供公有的靜態final常量。

不僅可以共享不可變對象,甚至也可以共享它們的內部信息

不可變對象爲其他對象提供了大量的構件 無論是可變的還是不可變的對象。如果知道一個複雜對象內部的組件對象不會改變,要維護它的不變性約束是比較容易的。這條原則的一種特例在于,不可變對象構成了大量的映射鍵(map key)和集合元素(set element); 一旦不可變對象進入到映射( map )或者集合( set)中,盡管這破壞了映射或者集合的不變性約束,但是也不用擔心它們的值會發生變化。

不可變對象無償地提供了失敗的原子性。 它們的狀態永遠不變,因此不存在臨時不一致的可能性 。

不可變類真正唯一的缺點是, 對于每個不同的值都需要一個單獨的對象。 創建這些對象的代價可能很高,特別是大型的對象。除非有令人信服的理由要使域變成是非 fina l 的,否則要使每個域都是 pr ivate final 的 。

第 18 條:複合優先于繼承

類依賴于其超類中特定功能的實現細節。超類的實現有可能會隨著發行版本的不同而有所變化,如果真的發生了變化,子類可能會遭到破壞,即使它的代碼完全沒有改變。 因而,子類必須要跟著其超類的更新而演變,除非超類是專門爲了擴展而設計的。

在新的類中增加一個私有域,它引用現有類的一個實例 。 這種設計被稱爲“複合”。

第 19 條:要麽設計繼承並提供文檔說明,要麽禁止繼承

如果不這麽做,子類就會依賴超類的實現細節,如果超類的實現發生了變化,它就有可能遭到破壞 。 爲了允許其他人能編寫出高效的子類,還你必須導出 一個或者多個受保護的方法 。 除非知道真正需要子類,否則最好通過將類聲明爲 final ,或者確保沒有可訪問的構造器來禁止類被繼承。

第 20 條:接口優于抽象類

第 21 條 : 爲後代設計接口

第 22 條:接口只用于定義類型

常量接口模式是對接口的不良使用,類在內部使用某些常量,這純粹是實現細節。實現常量接口會導致把這樣的實現細節泄露到該類的導出 API 中 。這樣做反而會使客戶端更加糊塗。可以使用枚舉代替

第 23 條 : 類層次優于標簽類

第 24 條 : 靜態成員類優于非靜態成員類

靜態成員類可以訪問外圍類的所有成員,包括那些聲明爲私有的成員 。 靜態成員類是外圍類的一個靜態成員,與其他的靜態成員一樣,也遵守同樣的可訪問性規則 。 如果它被聲明爲私有的,它就只能在外圍類的內部才可以被訪問。

非靜態成員類的每個實例都隱含地與外圍類的一個外圍實例相關聯 。 在非靜態成員類的實例方法內部,可以調用外圍實例上的方法,構造獲得外圍實例的引用。 如果嵌套類的實例可以在它外圍類的實例之外獨立存在,這個嵌套類就必須是靜態成員類:在沒有外圍實例的情況下,要想創建非靜態成員類的實例是不可能的 。當非靜態成員類的實例被創建的時候,它和外圍實例之間的關聯關系也隨之被建立起來;而且,這種關聯關系以後不能被修改。 通常情況下,當在外圍類的某個實例方法的內部調用非靜態成員類的構造器時,這種關聯關系被自動建立起來 。

匿名類不是外圍類的一個成員。它並不與其他的成員一起被聲明,而是在使用的同時被聲明和實例化 。 匿名類可以出現在代碼中任何允許存在表達式的地方 。 當且僅當匿名類出現在非靜態的環境中時,它才有外圍實例 。 但是即使它們出現在靜態的環境中,也不可能擁有任何靜態成員,而是 final 類型,或者被初始化成常量表達式的字符串域 。匿名類的運用受到諸多的限制 。 除了在它們被聲明的時候之外,是無法將它們實例化的。匿名類的客戶端無法調用任何成員。

第 25 條:限制j原文件爲單個頂級類

第 26 條:請不要使用原生態類型

如果使用原生態類型,就失掉了泛型在安全性和描述性方面的所有優勢,但原生類型可以保證兼容性。

新代碼中不要使用原生類型

?表示無限制類型的通配符

第 27 條:消除非受檢的警告

非受檢警告很重要 ,不要忽略它們 。 每一條警告都表示可能在運行時抛出ClassCastException 異常 。 要盡最大的努力消除這些警告 。 如果無法消除非受檢警告,同時可以證明引起警告的代碼是類型安全的 就可以在盡可能小的範圍內使用@ Suppress ­Warnings ( “unchecked ”)注解禁止該警告 。 要用注釋把禁止該警告的原因記錄下來 。

第 28 條:列表優于數組

創建泛型數組是非法的,因爲它不是類型安全的。要是它合法,編譯器在其他正確的程序中發生的轉換就會在運行時失敗,並出現-個 ClassCastException 異常 。這就違背了泛型系統提供的基本保證。

數組是協變(具有父子類型關系)且可以具體化的;泛型是不可變的且可以被擦除的 。 因此,數組提供了運行時的類型安全,但是沒有編譯時的類型安全,反之,對于泛型也一樣。 一般來說,數組和泛型不能很好地混合使用 。 如果你發現自己將它們混合起來使用,並且得到了編譯時錯誤或者警告,你的第一反應就應該是用列表代替數組。

第 29 條:優先考慮泛型

使用泛型比使用需要在客戶端代碼中進行轉換的類型來得更加安全,也更加容易 。 在設計新類型的時候,要確保它們不需要這種轉換就可以使用。這通常意味著要把類做成是泛型的 。 只要時間允許,就把現有的類型都泛型化 。 這對于這些類型的新用戶來說會變得更加輕松,又不會破壞現有的客戶端。

第 33 條:優先考慮類型安全的異構容器

限制每個容器只能有固定數目的類型參數 。 你可以通過將類型參數放在鍵上而不是容器上來避開這一限制 。 對于這種類型安全的異構容器,可以用 Class 對象作爲鍵 。 以這種方式使用的 Class 對象稱作類型令牌 。 你也可以使用定制的鍵類型 。 例如,用一個 DatabaseRow 類型表示一個數據庫行(容器),用泛型 Column<T>作爲它的鍵 。

第 34 條 : 用 enum 代替 int 常量

int 枚舉模式不具有類型安全性, 也幾乎沒有描述性可言 。

String 枚舉模式,同樣也不是我們期望的。它雖然爲這些常量提供了可打印的字符串,但是會導致初級用戶直接把字符串常量硬編碼到客戶端代碼中,而不是使用對應的常量字段名 。 一旦這樣的硬編碼字符串常量中包含書寫錯誤,在編譯時不會被檢測到,但是在運行的時候卻會報錯 。 而且它會導致性能問題,因爲它依賴于字符串的比較操作。

枚舉天生就是不可變的,因此所有的域都應該爲 final的

第 35 條:用實例域代替序數

永遠不要根據枚舉的序數導出與它關聯的值,而是要將它保存在一個實例域中。

第 36 條:用 EnumSet 代替位域

第 37 條:用 EnumMap 代替序數索引

第 38 條 : 用接 口 模擬可擴展的枚舉

雖然無法編寫可擴展的枚舉類型,卻可以通過編寫接口以及實現該接口的基礎枚舉類型來對它進行模擬。

第 39 條:注解優先于命名模式

命名模式( naming pattern)表明有些程序元素需要通過某種工具或者框架進行特殊處理。

第 40 條:堅持使用 Override 注解

如果在你想要的每個方法聲明中使用 Override 注解來覆蓋超類聲明,編譯器就可以替你防止大量的錯誤

第 41 條:用標記接口定義類型

標記接口( marker interface )是不包含方法聲明的接 口,它只是指明(或者“標明”) 一個類實現了具有某種屬性的接口 。 例如,考慮 Serializable 接口。

如果標記是應用于任何程序元素而不是類或者接口,就必須使用注解,因爲只有類和接口可以用來實現或者擴展接口。如果標記只應用于類和接口,就應該優先使用標記接口而非注解 。 這樣你就可以用接口作爲相關方法的參數類型,它可以真正爲你提供編譯時進行類型檢查的好處 。

第 42 條: Lambda 優先于匿名類

因爲編譯器是從泛型獲取到得以執行類型推導的大部分類型信息的 。 如果你沒有提供這些信息,編譯器就無法進行類型推導,你就必須在 Lambda 中手工指定類型,這樣極大地增加了它們的煩瑣程度 。 如果上述代碼片段中的變量 words 聲明爲原生態類型 List ,而不是參數化的類型 List<String>,它就不會進行編譯 。

Lambda 沒有名稱和文檔;如果一個計算本身不是自描述 的, 或者超 出了幾行, 那就不要把它旋在一個 Lambda中。

第 43 條:方法引用優先于 Lambda

從 Lambda 中提取代碼,放到-個新的方法中,並用該方法的一個引用代替 Lambda。 你可以給這個方法起一個有意義的名字,並用自己滿意的方式編寫進入文檔 。

方法引用常常 比 Lambda 表達式更加簡潔明了 。 只要方法引用更加簡潔、清晰,就用方法引用;如果方法引用並不簡潔,就堅持使用 Lambda 。

第 44 條:堅持使用標准的函數接 口

只要標准的函數接口能夠滿足需求,通常應該優先考慮,而不是專門再構建一個新的函數接口 。 這樣會使 API 更加容易學習,通過減少它的概念內容,顯著提升互操作性優勢,因爲許多標准的函數接口都提供了有用的默認方法。

Operator 接口代表其結果與參數類型一致的函數 。 Predicate 接口代表帶有一個參數並返回一個 boolean 的函數。 Function 接口代表其參數與返回的類型不一致的函數。Supplier 接口代表沒有參數並且返回(或“提供”)一個值的函數 。 最後, Consumer 代表的是帶有一個函數但不返回任何值的函數,相當于消費掉了參數。

最好不要用帶包裝類型的基礎函數接口來代替基本函數接口,會導致致命的性能問題 。

第 45 條:謹慎使用 Stream

從 Lambda 則只能讀取final或者有效的 final 變量,並且不能修改任何 local 變量

相關文章:

  • 阿裏問題定位神器 Arthas 操作實踐,定位線上BUG,超給力
  • 正則表達式在Java中的使用
  • 程序員:單個TCP(Socket)連接,發送多個文件
  • JDK8新特性你都用了嗎?
  • 面試清單(Java崗)Java+JVM+數據庫+算法+Spring+中間件+設計模式
  • Java 14 都快來了,爲什麽還有這麽多人固守Java 8?
歷史

發佈留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

©2026 快讀 | 服務協議 | DMCA | 聯繫我們