[TOC]
<!-- Appendix: Programming Guidelines -->
# 附錄:編程指南
> 本附錄包含了有助于指導你進行低級程序設計和編寫代碼的建議。
當然,這些只是指導方針,而不是規則。我們的想法是將它們用作靈感,并記住偶爾會違反這些指導方針的特殊情況。
<!-- Design -->
## 設計
1. **優雅總是會有回報**。從短期來看,似乎需要更長的時間才能找到一個真正優雅的問題解決方案,但是當該解決方案第一次應用并能輕松適應新情況,而不需要數小時,數天或數月的掙扎時,你會看到獎勵(即使沒有人可以測量它們)。它不僅為你提供了一個更容易構建和調試的程序,而且它也更容易理解和維護,這也正是經濟價值所在。這一點可以通過一些經驗來理解,因為當你想要使一段代碼變得優雅時,你可能看起來效率不是很高。抵制急于求成的沖動,它只會減慢你的速度。
2. **先讓它工作,然后再讓它變快**。即使你確定一段代碼非常重要并且它是你系統中的主要瓶頸**,也是如此。不要這樣做。使用盡可能簡單的設計使系統首先運行。然后如果速度不夠快,請對其進行分析。你幾乎總會發現“你的”瓶頸不是問題。節省時間,才是真正重要的東西。
3. **記住“分而治之”的原則**。如果所面臨的問題太過混亂**,就去想象一下程序的基本操作,因為存在一個處理困難部分的神奇“片段”(piece)。該“片段”是一個對象,編寫使用該對象的代碼,然后查看該對象并將其困難部分封裝到其他對象中,等等。
4. **將類創建者與類用戶(客戶端程序員)分開**。類用戶是“客戶”,不需要也不想知道類幕后發生了什么。類創建者必須是設計類的專家,他們編寫類,以便新手程序員都可以使用它,并仍然可以在應用程序中穩健地工作。將該類視為其他類的*服務提供者*(service provider)。只有對其它類透明,才能很容易地使用這個類。
5. **創建類時,給類起個清晰的名字,就算不需要注釋也能理解這個類**。你的目標應該是使客戶端程序員的接口在概念上變得簡單。為此,在適當時使用方法重載來創建直觀,易用的接口。
6. **你的分析和設計必須至少能夠產生系統中的類、它們的公共接口以及它們與其他類的關系,尤其是基類**。 如果你的設計方法產生的不止于此,就該問問自己,該方法生成的所有部分是否在程序的生命周期內都具有價值。如果不是,那么維護它們會很耗費精力。對于那些不會影響他們生產力的東西,開發團隊的成員往往不會去維護,這是許多設計方法都沒有考慮的生活現實。
7. **讓一切自動化**。首先在編寫類之前,編寫測試代碼,并將其與類保持一致。通過構建工具自動運行測試。你可能會使用事實上的標準Java構建工具Gradle。這樣,通過運行測試代碼可以自動驗證任何更改,將能夠立即發現錯誤。因為你知道自己擁有測試框架的安全網,所以當發現需要時,可以更大膽地進行徹底的更改。請記住,語言的巨大改進來自內置的測試,包括類型檢查,異常處理等,但這些內置功能很有限,你必須完成剩下的工作,針對具體的類或程序,去完善這些測試內容,從而創建一個強大的系統。
8. **在編寫類之前,先編寫測試代碼,以驗證類的設計是完善的**。如果不編寫測試代碼,那么就不知道類是什么樣的。此外,通過編寫測試代碼,往往能夠激發出類中所需的其他功能或約束。而這些功能或約束并不總是出現在分析和設計過程中。測試還會提供示例代碼,顯示了如何使用這個類。
9. **所有的軟件設計問題,都可以通過引入一個額外的間接概念層次(extra level of conceptual indirection)來解決**。這個軟件工程的基本規則[^1]是抽象的基礎,是面向對象編程的主要特征。在面向對象編程中,我們也可以這樣說:“如果你的代碼太復雜,就要生成更多的對象。”
10. **間接(indirection)應具有意義(與準則9一致)**。這個含義可以簡單到“將常用代碼放在單個方法中。”如果添加沒有意義的間接(抽象,封裝等)級別,那么它就像沒有足夠的間接性那樣糟糕。
11. **使類盡可能原子化**。 為每個類提供一個明確的目的,它為其他類提供一致的服務。如果你的類或系統設計變得過于復雜,請將復雜類分解為更簡單的類。最直觀的指標是尺寸大小,如果一個類很大,那么它可能是做的事太多了,應該被拆分。建議重新設計類的線索是:
- 一個復雜的*switch*語句:考慮使用多態。
- 大量方法涵蓋了很多不同類型的操作:考慮使用多個類。
- 大量成員變量涉及很多不同的特征:考慮使用多個類。
- 其他建議可以參見Martin Fowler的*Refactoring: Improving the Design of Existing Code*(重構:改善既有代碼的設計)(Addison-Wesley 1999)。
12. **注意長參數列表**。那樣方法調用會變得難以編寫,讀取和維護。相反,嘗試將方法移動到更合適的類,并且(或者)將對象作為參數傳遞。
13. **不要重復自己**。如果一段代碼出現在派生類的許多方法中,則將該代碼放入基類中的單個方法中,并從派生類方法中調用它。這樣不僅可以節省代碼空間,而且可以輕松地傳播更改。有時,發現這個通用代碼會為接口添加有價值的功能。此指南的更簡單版本也可以在沒有繼承的情況下發生:如果類具有重復代碼的方法,則將該重復代碼放入一個公共方,法并在其他方法中調用它。
14. **注意*switch*語句或鏈式*if-else*子句**。一個*類型檢查編碼*(type-check coding)的指示器意味著需要根據某種類型信息選擇要執行的代碼(確切的類型最初可能不明顯)。很多時候可以用繼承和多態替換這種代碼,多態方法調用將會執行類型檢查,并提供了更可靠和更容易的可擴展性。
15. **從設計的角度,尋找和分離那些因不變的事物而改變的事物**。也就是說,在不強制重新設計的情況下搜索可能想要更改的系統中的元素,然后將這些元素封裝在類中。
16. **不要通過子類擴展基本功能**。如果一個接口元素對于類來說是必不可少的,則它應該在基類中,而不是在派生期間添加。如果要在繼承期間添加方法,請考慮重新設計。
17. **少即是多**。從一個類的最小接口開始,盡可能小而簡單,以解決手頭的問題,但不要試圖預測類的所有使用方式。在使用該類時,就將會了解如何擴展接口。但是,一旦這個類已經在使用了,就無法在不破壞客戶端代碼的情況下縮小接口。如果必須添加更多方法,那很好,它不會破壞代碼。但即使新方法取代舊方法的功能,也只能是保留現有接口(如果需要,可以結合底層實現中的功能)。如果必須通過添加更多參數來擴展現有方法的接口,請使用新參數創建重載方法,這樣,就不會影響到對現有方法的任何調用。
18. **大聲讀出你的類以確保它們合乎邏輯**。將基類和派生類之間的關系稱為“is-a”,將成員對象稱為“has-a”。
19. **在需要在繼承和組合之間作決定時,問一下自己是否必須向上轉換為基類型**。如果不是,則使用組合(成員對象)更好。這可以消除對多種基類型的感知需求(perceived need)。如果使用繼承,則用戶會認為他們應該向上轉型。
20. **注意重載**。方法不應該基于參數的值而有條件地執行代碼。在這里,應該創建兩個或多個重載方法。
21. **使用異常層次結構**,最好是從標準Ja??va異常層次結構中的特定適當類派生。然后,捕獲異常的人可以為特定類型的異常編寫處理程序,然后為基類型編寫處理程序。如果添加新的派生異常,現有客戶端代碼仍將通過基類型捕獲異常。
22. **有時簡單的聚合可以完成工作**。航空公司的“乘客舒適系統”由獨立的元素組成:座位,空調,影視等,但必須在飛機上創建許多這樣的元素。你創建私有成員并建立一個全新的接口了嗎?如果不是,在這種情況下,組件也應該是公共接口的一部分,因此應該創建公共成員對象。這些對象有自己的私有實現,這些實現仍然是安全的。請注意,簡單聚合不是經常使用的解決方案,但確實會有時候會用到。
23. **考慮客戶程序員和維護代碼的人的觀點**。設計類以便盡可能直觀地被使用。預測要進行的更改,并精心設計類,以便輕松地進行更改。
24. **注意“巨型對象綜合癥”**(giant object syndrome)。這通常是程序員的痛苦,他們是面向對象編程的新手,總是編寫面向過程程序并將其粘貼在一個或兩個巨型對象中。除應用程序框架外,對象代表應用程序中的概念,而不是應用程序本身。
25. **如果你必須做一些丑陋的事情,至少要把類內的丑陋本地化**。
26. **如果必須做一些不可移植的事情,那就對這個事情做一個抽象,并在一個類中進行本地化**。這種額外的間接級別可防止在整個程序中擴散這種不可移植性。 (這個原則也體現在*橋接*模式中,等等)。
27. **對象不應該僅僅只是持有一些數據**。它們也應該有明確的行為。有時候,“數據傳輸對象”(data transfer objects)是合適的,但只有在泛型集合不合適時,才被明確用于打包和傳輸一組元素。
28. **在從現有類創建新類時首先選擇組合**。僅在設計需要時才使用繼承。如果在可以使用組合的地方使用繼承,那么設計將會變得很復雜,這是沒必要的。
29. **使用繼承和覆蓋方法來表達行為的差異,而不是使用字段來表示狀態的變化**。如果發現一個類使用了狀態變量,并且有一些方法是基于這些變量切換行為的,那么請重新設計它,以表示子類和覆蓋方法中的行為差異。一個極端的反例是繼承不同的類來表示顏色,而不是使用“顏色”字段。
30. **注意*協變*(variance)**。兩個語義不同的對象可能具有相同的操作或職責。為了從繼承中受益,會試圖讓其中一個成為另一個的子類,這是一種很自然的誘惑。這稱為協變,但沒有真正的理由去強制聲明一個并不存在的父子類關系。更好的解決方案是創建一個通用基類,并為兩者生成一個接口,使其成為這個通用基類的派生類。這仍然可以從繼承中受益,并且這可能是關于設計的一個重要發現。
31. **在繼承期間注意*限定*(limitation)**。最明確的設計為繼承的類增加了新的功能。含糊的設計在繼承期間刪除舊功能而不添加新功能。但是規則是用來打破的,如果是通過調用一個舊的類庫來工作,那么將一個現有類限制在其子類型中,可能比重構層次結構更有效,因此新類適合在舊類的上層。
32. **使用設計模式來消除“裸功能”(naked functionality)**。也就是說,如果類只需要創建一個對象,請不要推進應用程序并寫下注釋“只生成一個。”應該將其包裝成一個單例(singleton)。如果主程序中有很多亂七八糟的代碼去創建對象,那么找一個像工廠方法一樣的創建模式,可以在其中封裝創建過程。消除“裸功能”不僅會使代碼更易于理解和維護,而且還會使其能夠更加防范應對后面的善意維護者(well-intentioned maintainers)。
33. **注意“分析癱瘓”(analysis paralysis)**。記住,不得不經常在不了解整個項目的情況下推進項目,并且通常了解那些未知因素的最好、最快的方式是進入下一步而不是嘗試在腦海中弄清楚。在獲得解決方案之前,往往無法知道解決方案。Java有內置的防火墻,讓它們為你工作。你在一個類或一組類中的錯誤不會破壞整個系統的完整性。
34. **如果認為自己有很好的分析,設計或實施,請做一個演練**。從團隊外部帶來一些人,不一定是顧問,但可以是公司內其他團體的人。用一雙新眼睛評審你的工作,可以在一個更容易修復它們的階段發現問題,而不僅僅是把大量時間和金錢全扔到演練過程中。
<!-- Implementation -->
## 實現
36. **遵循編碼慣例**。有很多不同的約定,例如,[谷歌使用的約定](https://google.github.io/styleguide/javaguide.html)(本書中的代碼盡可能地遵循這些約定)。如果堅持使用其他語言的編碼風格,那么讀者就會很難去閱讀。無論決定采用何種編碼約定,都要確保它們在整個項目中保持一致。集成開發環境通常包含內置的重新格式化(reformatter)和檢查器(checker)。
37. **無論使用何種編碼風格,如果你的團隊(甚至更好是公司)對其進行標準化,它就確實會產生重大影響**。這意味著,如果不符合這個標準,那么每個人都認為修復別人的編碼風格是公平的游戲。標準化的價值在于解析代碼可以花費較少的腦力,因此可以更專注于代碼的含義。
38. **遵循標準的大寫規則**。類名的第一個字母大寫。字段,方法和對象(引用)的第一個字母應為小寫。所有標識符應該將各個單詞組合在一起,并將所有中間單詞的首字母大寫。例如:
- **ThisIsAClassName**
- **thisIsAMethodOrFieldName**
將 **static final** 類型的標識符的所有字母全部大寫,并用下劃線分隔各個單詞,這些標識符在其定義中具有常量初始值。這表明它們是編譯時常量。
- **包是一個特例**,它們都是小寫的字母,即使是中間詞。域擴展(com,org,net,edu等)也應該是小寫的。這是Java 1.1和Java 2之間的變化。
39. **不要創建自己的“裝飾”私有字段名稱**。這通常以前置下劃線和字符的形式出現。匈牙利命名法(譯者注:一種命名規范,基本原則是:變量名=屬性+類型+對象描述。Win32程序風格采用這種命名法,如`WORD wParam1;LONG lParam2;HANDLE hInstance`)是最糟糕的例子,你可以在其中附加額外字符用于指示數據類型,用途,位置等,就好像你正在編寫匯編語言一樣,編譯器根本沒有提供額外的幫助。這些符號令人困惑,難以閱讀,并且難以執行和維護。讓類和包來指定名稱范圍。如果認為必須裝飾名稱以防止混淆,那么代碼就可能過于混亂,這應該被簡化。
40. 在創建一般用途的類時,**遵循“規范形式”**。包括**equals()**,**hashCode()**,**toString()**,**clone()**的定義(實現**Cloneable**,或選擇其他一些對象復制方法,如序列化),并實現**Comparable**和**Serializable**。
41. **對讀取和更改私有字段的方法使用“get”,“set”和“is”命名約定**。這種做法不僅使類易于使用,而且也是命名這些方法的標準方法,因此讀者更容易理解。
42. **對于所創建的每個類,請包含該類的JUnit測試**(請參閱*junit.org*以及[第十六章:代碼校驗]()中的示例)。無需刪除測試代碼即可在項目中使用該類,如果進行更改,則可以輕松地重新運行測試。測試代碼也能成為如何使用這個類的示例。
43. **有時需要繼承才能訪問基類的protected成員**。這可能導致對多種基類型的感知需求(perceived need)。如果不需要向上轉型,則可以首先派生一個新類來執行受保護的訪問。然后把該新類作為使用它的任何類中的成員對象,以此來代替直接繼承。
44. **為了提高效率,避免使用*final*方法**。只有在分析后發現方法調用是瓶頸時,才將**final**用于此目的。
45. **如果兩個類以某種功能方式相互關聯(例如集合和迭代器),則嘗試使一個類成為另一個類的內部類**。這不僅強調了類之間的關聯,而且通過將類嵌套在另一個類中,可以允許在單個包中重用類名。Java集合庫通過在每個集合類中定義內部**Iterator**類來實現此目的,從而為集合提供通用接口。使用內部類的另一個原因是作為**私有**實現的一部分。這里,內部類將有利于實現隱藏,而不是上面提到的類關聯和防止命名空間污染。
46. **只要你注意到類似乎彼此之間具有高耦合,請考慮如果使用內部類可能獲得的編碼和維護改進**。內部類的使用不會解耦類,而是明確耦合關系,并且更方便。
47. **不要成為過早優化的犧牲品**。過早優化是很瘋狂的行為。特別是,不要擔心編寫(或避免)本機方法(native methods),將某些方法設置為**final**,或者在首次構建系統時調整代碼以使其高效。你的主要目標應該是驗證設計。即使設計需要一定的效率,也*先讓它工作,然后再讓它變快*。
48. **保持作用域盡可能小,以便能見度和對象的壽命盡可能小**。這減少了在錯誤的上下文中使用對象并隱藏了難以發現的bug的機會。例如,假設有一個集合和一段迭代它的代碼。如果復制該代碼以用于一個新集合,那么可能會意外地將舊集合的大小用作新集合的迭代上限。但是,如果舊集合比較大,則會在編譯時捕獲錯誤。
49. **使用標準Java庫中的集合**。熟練使用它們,將會大大提高工作效率。首選**ArrayList**用于序列,**HashSet**用于集合,**HashMap**用于關聯數組,**LinkedList**用于堆棧(而不是**Stack**,盡管也可以創建一個適配器來提供堆棧接口)和隊列(也可以使用適配器,如本書所示)。當使用前三個時,將其分別向上轉型為**List**,**Set**和**Map**,那么就可以根據需要輕松更改為其他實現。
50. **為使整個程序健壯,每個組件必須健壯**。在所創建的每個類中,使用Java所提供的所有工具,如訪問控制,異常,類型檢查,同步等。這樣,就可以在構建系統時安全地進入下一級抽象。
51. **編譯時錯誤優于運行時錯誤**。嘗試盡可能在錯誤發生點處理錯誤。在最近的處理程序中盡其所能地捕獲它能處理的所有異常。在當前層面處理所能處理的所有異常,如果解決不了,就重新拋出異常。
52. **注意長方法定義**。方法應該是簡短的功能單元,用于描述和實現類接口的離散部分。維護一個冗長而復雜的方法是很困難的,而且代價很大,并且這個方法可能是試圖做了太多事情。如果看到這樣的方法,這表明,至少應該將它分解為多種方法。也可能建議去創建一個新類。小的方法也可以促進類重用。(有時方法必須很大,但它們應該只做一件事。)
53. **保持“盡可能私有”**。一旦公開了你的類庫中的一個方面(一個方法,一個類,一個字段),你就永遠無法把它拿回來。如果這樣做,就將破壞某些人的現有代碼,迫使他們重寫和重新設計。如果你只公開了必須公開的內容,就可以輕易地改變其他一切,而不會對其他人造成影響,而且由于設計趨于發展,這是一個重要的自由。通過這種方式,更改具體實現將對派生類造成的影響最小。在處理多線程時,私有尤其重要,只有**私有**字段可以防止不同步使用。具有包訪問權限的類應該仍然具有**私有**字段,但通常有必要提供包訪問權限的方法而不是將它們**公開**。
54. **大量使用注釋,并使用*Javadoc commentdocumentation*語法生成程序文檔**。但是,注釋應該為代碼增加真正的意義,如果注釋只是重申了代碼已經清楚表達的內容,這是令人討厭的。請注意,Java類和方法名稱的典型詳細信息減少了對某些注釋的需求。
55. **避免使用“魔法數字”**。這些是指硬編碼到代碼中的數字。如果后續必須要更改它們,那將是一場噩夢,因為你永遠不知道“100”是指“數組大小”還是“完全不同的東西”。相反,創建一個帶有描述性名稱的常量并在整個程序中使用常量標識符。這使程序更易于理解,更易于維護。
56. **在創建構造方法時,請考慮異常**。最好的情況是,構造方法不會做任何拋出異常的事情。次一級的最佳方案是,該類僅由健壯的類組成或繼承自健壯的類,因此如果拋出異常則不需要處理。否則,必須清除**finally**子句中的組合類。如果構造方法必然失敗,則適當的操作是拋出異常,因此調用者不會認為該對象是正確創建的而盲目地繼續下去。
57. **在構造方法內部,只需要將對象設置為正確的狀態**。主動避免調用其他方法(**final**方法除外),因為這些方法可以被其他人覆蓋,從而在構造期間產生意外結果。(有關詳細信息,請參閱[第六章:初始化和清理]()章節。)較小,較簡單的構造方法不太可能拋出異常或導致問題。
58. **如果類在客戶端程序員用完對象時需要進行任何清理,請將清理代碼放在一個明確定義的方法中**,并使用像 **dispose()** 這樣的名稱來清楚地表明其目的。另外,在類中放置一個 **boolean** 標志來指示是否調用了 **dispose()** ,因此 **finalize()** 可以檢查“終止條件”(參見[第六章:初始化和清理]()章節)。
59. ***finalize()* 的職責只能是驗證對象的“終止條件”以進行調試**。(參見[第六章:初始化和清理]()一章)在特殊情況下,可能需要釋放垃圾收集器無法釋放的內存。因為可能無法為對象調用垃圾收集器,所以無法使用 **finalize()** 執行必要的清理。為此,必須創建自己的 **dispose()** 方法。在類的 **finalize()** 方法中,檢查以確保對象已被清理,如果沒有被清理,則拋出一個派生自**RuntimeException**的異常,以指示編程錯誤。在依賴這樣的計劃之前,請確保 **finalize()** 適用于你的系統。(可能需要調用 **System.gc()** 來確保此行為。)
60. **如果必須在特定范圍內清理對象(除了通過垃圾收集器),請使用以下準則:** 初始化對象,如果成功,立即進入一個帶有 **finally** 子句的 **try** 塊,并在 **finally**中執行清理操作。
61. **在繼承期間覆蓋 *finalize()* 時,記得調用 *super.finalize()***。(如果是直接繼承自 **Object** 則不需要這樣做。)調用 **super.finalize()** 作為重寫的 **finalize()** 的最終行為而不是在第一行調用它,這樣可以確保基類組件在需要時仍然有效。
62. **創建固定大小的對象集合時,將它們轉換為數組,** 尤其是在從方法中返回此集合時。這樣就可以獲得數組編譯時類型檢查的好處,并且數組的接收者可能不需要在數組中強制轉換對象來使用它們。請注意,集合庫的基類 **java.util.Collection** 有兩個 **toArray()** 方法來完成此任務。
63. **優先選擇 *接口* 而不是 *抽象類***。如果知道某些東西應該是基類,那么第一選擇應該是使其成為一個接口,并且只有在需要方法定義或成員變量時才將其更改為抽象類。一個接口關心客戶端想要做什么,而一個類傾向于關注(或允許)實現細節。
64. **為了避免非常令人沮喪的經歷,請確保類路徑中的每個名稱只對應一個不在包中的類**。否則,編譯器可以首先找到具有相同名稱的其他類,并報告沒有意義的錯誤消息。如果你懷疑自己有類路徑問題,請嘗試在類路徑的每個起始點查找具有相同名稱的 **.class** 文件。理想情況下,應該將所有類放在包中。
65. **注意意外重載**。如果嘗試覆蓋基類方法但是拼寫錯誤,則最終會添加新方法而不是覆蓋現有方法。但是,這是完全合法的,因此在編譯時或運行時不會獲得任何錯誤消息,但代碼將無法正常工作。始終使用 **@Override** 注釋來防止這種情況。
66. **注意過早優化**。先讓它工作,然后再讓它變快。除非發現代碼的特定部分存在性能瓶頸。除非是使用分析器發現瓶頸,否則過早優化會浪費時間。性能調整所隱藏的額外成本是代碼將變得難以理解和維護。
67. **請注意,相比于編寫代碼,代碼被閱讀的機會更多**。清晰的設計可能產生易于理解的程序,但注釋,詳細解釋,測試和示例是非常寶貴的,它們可以幫助你和你的所有后繼者。如果不出意外,試圖從JDK文檔中找出有用信息的挫敗感應該可以說服你。
[^1]: Andrew Koenig向我解釋了它。
<!-- 分頁 -->
<div style="page-break-after: always;"></div>
- 譯者的話
- 前言
- 簡介
- 第一章 對象的概念
- 抽象
- 接口
- 服務提供
- 封裝
- 復用
- 繼承
- "是一個"與"像是一個"的關系
- 多態
- 單繼承結構
- 集合
- 對象創建與生命周期
- 異常處理
- 本章小結
- 第二章 安裝Java和本書用例
- 編輯器
- Shell
- Java安裝
- 校驗安裝
- 安裝和運行代碼示例
- 第三章 萬物皆對象
- 對象操縱
- 對象創建
- 數據存儲
- 基本類型的存儲
- 高精度數值
- 數組的存儲
- 代碼注釋
- 對象清理
- 作用域
- 對象作用域
- 類的創建
- 類型
- 字段
- 基本類型默認值
- 方法使用
- 返回類型
- 參數列表
- 程序編寫
- 命名可見性
- 使用其他組件
- static關鍵字
- 小試牛刀
- 編譯和運行
- 編碼風格
- 本章小結
- 第四章 運算符
- 開始使用
- 優先級
- 賦值
- 方法調用中的別名現象
- 算術運算符
- 一元加減運算符
- 遞增和遞減
- 關系運算符
- 測試對象等價
- 邏輯運算符
- 短路
- 字面值常量
- 下劃線
- 指數計數法
- 位運算符
- 移位運算符
- 三元運算符
- 字符串運算符
- 常見陷阱
- 類型轉換
- 截斷和舍入
- 類型提升
- Java沒有sizeof
- 運算符總結
- 本章小結
- 第五章 控制流
- true和false
- if-else
- 迭代語句
- while
- do-while
- for
- 逗號操作符
- for-in 語法
- return
- break 和 continue
- 臭名昭著的 goto
- switch
- switch 字符串
- 本章小結
- 第六章 初始化和清理
- 利用構造器保證初始化
- 方法重載
- 區分重載方法
- 重載與基本類型
- 返回值的重載
- 無參構造器
- this關鍵字
- 在構造器中調用構造器
- static 的含義
- 垃圾回收器
- finalize()的用途
- 你必須實施清理
- 終結條件
- 垃圾回收器如何工作
- 成員初始化
- 指定初始化
- 構造器初始化
- 初始化的順序
- 靜態數據的初始化
- 顯式的靜態初始化
- 非靜態實例初始化
- 數組初始化
- 動態數組創建
- 可變參數列表
- 枚舉類型
- 本章小結
- 第七章 封裝
- 包的概念
- 代碼組織
- 創建獨一無二的包名
- 沖突
- 定制工具庫
- 使用 import 改變行為
- 使用包的忠告
- 訪問權限修飾符
- 包訪問權限
- public: 接口訪問權限
- 默認包
- private: 你無法訪問
- protected: 繼承訪問權限
- 包訪問權限 Vs Public 構造器
- 接口和實現
- 類訪問權限
- 本章小結
- 第八章 復用
- 組合語法
- 繼承語法
- 初始化基類
- 帶參數的構造函數
- 委托
- 結合組合與繼承
- 保證適當的清理
- 名稱隱藏
- 組合與繼承的選擇
- protected
- 向上轉型
- 再論組合和繼承
- final關鍵字
- final 數據
- 空白 final
- final 參數
- final 方法
- final 和 private
- final 類
- final 忠告
- 類初始化和加載
- 繼承和初始化
- 本章小結
- 第九章 多態
- 向上轉型回顧
- 忘掉對象類型
- 轉機
- 方法調用綁定
- 產生正確的行為
- 可擴展性
- 陷阱:“重寫”私有方法
- 陷阱:屬性與靜態方法
- 構造器和多態
- 構造器調用順序
- 繼承和清理
- 構造器內部多態方法的行為
- 協變返回類型
- 使用繼承設計
- 替代 vs 擴展
- 向下轉型與運行時類型信息
- 本章小結
- 第十章 接口
- 抽象類和方法
- 接口創建
- 默認方法
- 多繼承
- 接口中的靜態方法
- Instrument 作為接口
- 抽象類和接口
- 完全解耦
- 多接口結合
- 使用繼承擴展接口
- 結合接口時的命名沖突
- 接口適配
- 接口字段
- 初始化接口中的字段
- 接口嵌套
- 接口和工廠方法模式
- 本章小結
- 第十一章 內部類
- 創建內部類
- 鏈接外部類
- 使用 .this 和 .new
- 內部類與向上轉型
- 內部類方法和作用域
- 匿名內部類
- 嵌套類
- 接口內部的類
- 從多層嵌套類中訪問外部類的成員
- 為什么需要內部類
- 閉包與回調
- 內部類與控制框架
- 繼承內部類
- 內部類可以被覆蓋么?
- 局部內部類
- 內部類標識符
- 本章小結
- 第十二章 集合
- 泛型和類型安全的集合
- 基本概念
- 添加元素組
- 集合的打印
- 迭代器Iterators
- ListIterator
- 鏈表LinkedList
- 堆棧Stack
- 集合Set
- 映射Map
- 隊列Queue
- 優先級隊列PriorityQueue
- 集合與迭代器
- for-in和迭代器
- 適配器方法慣用法
- 本章小結
- 簡單集合分類
- 第十三章 函數式編程
- 新舊對比
- Lambda表達式
- 遞歸
- 方法引用
- Runnable接口
- 未綁定的方法引用
- 構造函數引用
- 函數式接口
- 多參數函數式接口
- 缺少基本類型的函數
- 高階函數
- 閉包
- 作為閉包的內部類
- 函數組合
- 柯里化和部分求值
- 純函數式編程
- 本章小結
- 第十四章 流式編程
- 流支持
- 流創建
- 隨機數流
- int 類型的范圍
- generate()
- iterate()
- 流的建造者模式
- Arrays
- 正則表達式
- 中間操作
- 跟蹤和調試
- 流元素排序
- 移除元素
- 應用函數到元素
- 在map()中組合流
- Optional類
- 便利函數
- 創建 Optional
- Optional 對象操作
- Optional 流
- 終端操作
- 數組
- 集合
- 組合
- 匹配
- 查找
- 信息
- 數字流信息
- 本章小結
- 第十五章 異常
- 異常概念
- 基本異常
- 異常參數
- 異常捕獲
- try 語句塊
- 異常處理程序
- 終止與恢復
- 自定義異常
- 異常與記錄日志
- 異常聲明
- 捕獲所有異常
- 多重捕獲
- 棧軌跡
- 重新拋出異常
- 精準的重新拋出異常
- 異常鏈
- Java 標準異常
- 特例:RuntimeException
- 使用 finally 進行清理
- finally 用來做什么?
- 在 return 中使用 finally
- 缺憾:異常丟失
- 異常限制
- 構造器
- Try-With-Resources 用法
- 揭示細節
- 異常匹配
- 其他可選方式
- 歷史
- 觀點
- 把異常傳遞給控制臺
- 把“被檢查的異常”轉換為“不檢查的異常”
- 異常指南
- 本章小結
- 后記:Exception Bizarro World
- 第十六章 代碼校驗
- 測試
- 如果沒有測試過,它就是不能工作的
- 單元測試
- JUnit
- 測試覆蓋率的幻覺
- 前置條件
- 斷言(Assertions)
- Java 斷言語法
- Guava斷言
- 使用斷言進行契約式設計
- 檢查指令
- 前置條件
- 后置條件
- 不變性
- 放松 DbC 檢查或非嚴格的 DbC
- DbC + 單元測試
- 使用Guava前置條件
- 測試驅動開發
- 測試驅動 vs. 測試優先
- 日志
- 日志會給出正在運行的程序的各種信息
- 日志等級
- 調試
- 使用 JDB 調試
- 圖形化調試器
- 基準測試
- 微基準測試
- JMH 的引入
- 剖析和優化
- 優化準則
- 風格檢測
- 靜態錯誤分析
- 代碼重審
- 結對編程
- 重構
- 重構基石
- 持續集成
- 本章小結
- 第十七章 文件
- 文件和目錄路徑
- 選取路徑部分片段
- 路徑分析
- Paths的增減修改
- 目錄
- 文件系統
- 路徑監聽
- 文件查找
- 文件讀寫
- 本章小結
- 第十八章 字符串
- 字符串的不可變
- +的重載與StringBuilder
- 意外遞歸
- 字符串操作
- 格式化輸出
- printf()
- System.out.format()
- Formatter類
- 格式化修飾符
- Formatter轉換
- String.format()
- 一個十六進制轉儲(dump)工具
- 正則表達式
- 基礎
- 創建正則表達式
- 量詞
- CharSequence
- Pattern和Matcher
- find()
- 組(Groups)
- start()和end()
- Pattern標記
- split()
- 替換操作
- 正則表達式與 Java I/O
- 掃描輸入
- Scanner分隔符
- 用正則表達式掃描
- StringTokenizer類
- 本章小結
- 第十九章 類型信息
- 為什么需要 RTTI
- Class對象
- 類字面常量
- 泛化的Class引用
- cast()方法
- 類型轉換檢測
- 使用類字面量
- 遞歸計數
- 一個動態instanceof函數
- 注冊工廠
- 類的等價比較
- 反射:運行時類信息
- 類方法提取器
- 動態代理
- Optional類
- 標記接口
- Mock 對象和樁
- 接口和類型
- 本章小結
- 第二十章 泛型
- 簡單泛型
- 泛型接口
- 泛型方法
- 復雜模型構建
- 泛型擦除
- 補償擦除
- 邊界
- 通配符
- 問題
- 自限定的類型
- 動態類型安全
- 泛型異常
- 混型
- 潛在類型機制
- 對缺乏潛在類型機制的補償
- Java8 中的輔助潛在類型
- 總結:類型轉換真的如此之糟嗎?
- 進階閱讀
- 第二十一章 數組
- 數組特性
- 一等對象
- 返回數組
- 多維數組
- 泛型數組
- Arrays的fill方法
- Arrays的setAll方法
- 增量生成
- 隨機生成
- 泛型和基本數組
- 數組元素修改
- 數組并行
- Arrays工具類
- 數組比較
- 數組拷貝
- 流和數組
- 數組排序
- Arrays.sort()的使用
- 并行排序
- binarySearch二分查找
- parallelPrefix并行前綴
- 本章小結
- 第二十二章 枚舉
- 基本 enum 特性
- 將靜態類型導入用于 enum
- 方法添加
- 覆蓋 enum 的方法
- switch 語句中的 enum
- values 方法的神秘之處
- 實現而非繼承
- 隨機選擇
- 使用接口組織枚舉
- 使用 EnumSet 替代 Flags
- 使用 EnumMap
- 常量特定方法
- 使用 enum 的職責鏈
- 使用 enum 的狀態機
- 多路分發
- 使用 enum 分發
- 使用常量相關的方法
- 使用 EnumMap 進行分發
- 使用二維數組
- 本章小結
- 第二十三章 注解
- 基本語法
- 定義注解
- 元注解
- 編寫注解處理器
- 注解元素
- 默認值限制
- 替代方案
- 注解不支持繼承
- 實現處理器
- 使用javac處理注解
- 最簡單的處理器
- 更復雜的處理器
- 基于注解的單元測試
- 在 @Unit 中使用泛型
- 實現 @Unit
- 本章小結
- 第二十四章 并發編程
- 術語問題
- 并發的新定義
- 并發的超能力
- 并發為速度而生
- 四句格言
- 1.不要這樣做
- 2.沒有什么是真的,一切可能都有問題
- 3.它起作用,并不意味著它沒有問題
- 4.你必須仍然理解
- 殘酷的真相
- 本章其余部分
- 并行流
- 創建和運行任務
- 終止耗時任務
- CompletableFuture類
- 基本用法
- 結合 CompletableFuture
- 模擬
- 異常
- 流異常(Stream Exception)
- 檢查性異常
- 死鎖
- 構造方法非線程安全
- 復雜性和代價
- 本章小結
- 缺點
- 共享內存陷阱
- This Albatross is Big
- 其他類庫
- 考慮為并發設計的語言
- 拓展閱讀
- 第二十五章 設計模式
- 概念
- 單例模式
- 模式分類
- 構建應用程序框架
- 面向實現
- 工廠模式
- 動態工廠
- 多態工廠
- 抽象工廠
- 函數對象
- 命令模式
- 策略模式
- 責任鏈模式
- 改變接口
- 適配器模式(Adapter)
- 外觀模式(Fa?ade)
- 包(Package)作為外觀模式的變體
- 解釋器:運行時的彈性
- 回調
- 多次調度
- 模式重構
- 抽象用法
- 多次派遣
- 訪問者模式
- RTTI的優劣
- 本章小結
- 附錄:補充
- 附錄:編程指南
- 附錄:文檔注釋
- 附錄:對象傳遞和返回
- 附錄:流式IO
- 輸入流類型
- 輸出流類型
- 添加屬性和有用的接口
- 通過FilterInputStream 從 InputStream 讀取
- 通過 FilterOutputStream 向 OutputStream 寫入
- Reader和Writer
- 數據的來源和去處
- 更改流的行為
- 未發生改變的類
- RandomAccessFile類
- IO流典型用途
- 緩沖輸入文件
- 從內存輸入
- 格式化內存輸入
- 基本文件的輸出
- 文本文件輸出快捷方式
- 存儲和恢復數據
- 讀寫隨機訪問文件
- 本章小結
- 附錄:標準IO
- 附錄:新IO
- ByteBuffer
- 數據轉換
- 基本類型獲取
- 視圖緩沖區
- 字節存儲次序
- 緩沖區數據操作
- 緩沖區細節
- 內存映射文件
- 性能
- 文件鎖定
- 映射文件的部分鎖定
- 附錄:理解equals和hashCode方法
- 附錄:集合主題
- 附錄:并發底層原理
- 附錄:數據壓縮
- 附錄:對象序列化
- 附錄:靜態語言類型檢查
- 附錄:C++和Java的優良傳統
- 附錄:成為一名程序員