《OnJava8》精讀(一) 一切的開始:對象

在這裏插入圖片描述

一切的開始

《On Java 8》是什麼?

它是《Thinking In Java》的作者Bruce Eckel基於Java8寫的新書。裏面包含了對Java深入的理解及思想維度的理念。可以比作Java界的“武學祕籍”。任何Java語言的使用者,甚至是非Java使用者但是對面向對象思想有興趣的程序員都該一讀的經典書籍。目前豆瓣評分9.5,是公認的編程經典。
豆瓣評分

爲什麼要讀書學習編程?

我認爲編程的學習分爲兩種:

  • 實際項目開發
  • 書籍理論的學習

這兩種各有優缺點。實際開發學習效率最快,但是學習面不夠廣,項目中遇到的問題會被深刻記憶,但是沒有遇到的問題(或者被規避的問題)卻不能被掌握。
相反,書籍學習可以地毯式的、條理式的學習,但是過程漫長,缺乏一定的目標的話,容易中斷。
但是,書籍的學習還是有必要。基礎知識的學習永遠都不過時。

爲什麼要寫這個系列的精讀博文?

如上個問題提到的,由於書籍讀起來時間久,過程漫長,因此產生了寫本精讀系列的最初想法。除此之外,最重要的一個原因是,由於中文版是譯版,讀起來還是有較大的生硬感(這種差異並非譯者的翻譯問題,類似英文無法譯出唐詩),有時候這導致我們對理解作者意圖需要一點推敲。再加上原書的內容很長,只第一章就多達一萬多字(不含代碼),讀起來就需要大量時間。

所以,如果現在有一個人能替我們先仔細讀一遍,篩選出其中的精華,讓我們可以在地鐵上或者路上不用花太多時間就可以瞭解這邊經典書籍的思想那就最好不過了。於是這個系列誕生了。

本系列博文適合那些人讀?

只要是對編程思想感興趣的都可以。對學生和剛畢業的人來說可以起到引領作用。對已經有多年編程經驗的人來說,溫故知新,基礎知識在本書作者這樣的大佬頭腦裏可能爆發出不同的思想,也能使你得到一些啓發。

一些建議

着重建議讀本書的英文版原文。此外,也可以參考本書的中文譯版。我在寫這個系列的時候,會盡量的保證以“陳述句”的方式表達書的內容,我也會寫出自己的部分觀點,但是這種觀點會保持理性並儘量少而精。本系列中對於原作的內容會以引用的方式體現。

最重要的一點,大家可以通過博客平臺的評論功能多加交流,這也是學習的一個重要環節。就像作者在前言中說的那樣:

與幾年前我們依賴印刷媒體相比,YouTube,博客和 StackOverflow 等網站的出現讓尋找答案變得簡單。請結合這些學習途徑和努力堅持下去。

前言及簡介

本章總字數:7600

關鍵詞:

  • 本書基於 Java 8 版本
  • 本書的最低要求需要懂得基本的語法概念,如:if、while和方法等
  • 教學目標:循序漸進、更簡單的講解、不過多研究細節
  • 本書可能會提到語言的缺陷,希望讀者理性看待(怕引戰)
  • Java的圖形界面很失敗

在前言裏作者主要說明的寫書的初衷以及感嘆與他自己的大名鼎鼎的《Thinking In Java》寫作時期Java版本的變化。

作者着重說了自己的教學目標,希望自己的書可以讓讀者循序漸進的、以更簡單的方式學到更多東西。在簡介中,作者的一句話很重要:我認爲信息的重要性是分層次結構的。絕大多數情況下,我們沒必要弄清問題的所有本質。

這就註定了本書的內容是儘量在不糾結語言設計細節的基礎上講解。

除此之外,作者還強調了“承認錯誤”的重要性。認爲現在的程序員已經不再把語言當做一個工具而是一個陣營,對於提出語言設計缺陷的人進行攻擊。

看過我此前作品的讀者們應該清楚,我一般傾向於指出這些錯誤。Java 擁有一批狂熱的粉絲。他們把語言當成是陣營而不是純粹的編程工具。我寫過 Java 書籍,所以他們兀自認爲我自然也是這個“陣營”的一份子。當我指出 Java 的這些錯誤時,會造成兩種影響:

早先許多錯誤“陣營”的人成爲了犧牲品。最終,時隔多年後,大家都意識到這是個設計上的錯誤。然而錯誤已然成爲 Java 歷史的一部分了。

更重要的是,新手程序員並沒有經歷過“語言爲何採用某種方式實現”的爭議過程。特別是那些隱約察覺不對卻依然說服自己“我必須要這麼做”或“我只是沒學明白”從而繼續錯下去的人。更糟糕的是,教授這些編程知識的老師們沒能深入地去研究這裏是否有設計上的錯誤,而是繼續錯誤的解讀。總之,通過了解語言設計上的錯誤,能讓開發者們更好地理解和意識到錯誤的本質,從而更快地進步。

這段話讓我想起了“XXX是世界上最好的語言”這種古老的段子。雖然大部分時候我們的這種爭論只是玩笑,但是確實也在論壇圈子中見過各種各樣關於語言的爭吵。我剛畢業時是做C#,在很久之前關於C#和Java的對比也爭執過。後來漸漸發現很多程序員容不得別人說自己所用語言的不好(無論是那種語言),這一點明顯體現在工作3~5年這區間中。因爲剛剛畢業的人對語言的執着還不是很深,而工作已經很久的大佬已經不在乎語言的特性而更關注於哪種語言更合適做某件事情。

所以從作者的這句話中已經能感受到,作者的格局思想超出了語言本身,而是將語言作爲工具的辯證思維。既然是工具,當然就有其優缺點,這是很正常的事情。如果某一個萬能語言可以完美到沒有任何問題,又怎麼會出現這麼多編程語言?語言之所層出不窮,是因爲不同的場景下適用的編程思想不同

因此,對比作者的理性思考,我們從書中應該學習他的這種辯證思維。

在簡介的最後一部分,作者提到了Java的圖形界面,並認爲其是“設計失敗的絕佳教材”。

桌面程序領域似乎從未嘗勾起 Java 設計師的野心。Java 沒有在圖形界面取得該有的一席之地。另外,曾被大肆吹噓的 JavaBeans 也沒有獲得任何影響力。(許多不幸的作者花了很多精力在 Swing 上編寫書籍,甚至只用 JavaBeans 編寫書籍)。Java 圖形界面程序大多數情況下僅用於 IDE(集成開發環境)和一些企業內部應用程序。你可以採用 Java 開發圖形界面,但這並非 Java 最擅長的領域。

作者的意思是指本書不會着重圖形界面方面的知識,且說了圖形界面不是Java的優勢。以免想了解Java圖像界面知識的人讀了這本書。

第一章 對象的概念

本章總字數:14000

關鍵詞:

  • 抽象是解決問題的基石
  • Smalltalk是第一個成功的面嚮對象語言並影響了後來的Java
  • “類型”與“接口”的對應關係是面向對象程序設計的基礎
  • 訪問控制有效的實現了應用程序員不觸摸他們不應該觸摸的部分代碼
  • 繼承不應當隨處可見,而“組合”更加靈活
  • 在派生中,"is-a"和"is-like-a"有區別
  • 掌握多態可以使我們的編程順利、高效
  • 集合和泛型的使得數據的存儲、處理變得簡便
  • 在Java中對象的生命週期管理更簡單(自動垃圾回收)

本章是總領性的概念性章節。篇幅在全書中也算是較長的。主要闡述了面向對象的基礎概念,包括:抽象、接口、類、繼承等,也提到了常用的一些知識,如:集合、泛型、異常處理及垃圾回收。

在本章的開始,作者說明了本章內容是講解OOP基礎的,如果讀者看不懂可以跳過以後再讀。我認爲作者將本章放置在所有章節的前面是很明智的。對於有一定基礎的讀者,本章可以作爲重溫,對於開始接觸編程的讀者,本篇也可以讓人大體有一定了解。

作者提到了抽象是解決問題的基礎所在。正是因爲有了抽象的概念,才衍生出了對象。並提到了Smalltalk語言,Smalltalk 是一個面嚮對象語言,比Java早。它的一些概念很大程度影響到了後來的Java。比如對象的思想:

  • 萬物皆對象。你可以將對象想象成一種特殊的變量。它存儲數據,但可以在你對其“發出請求”時執行本身的操作。理論上講,你總是可以從要解決的問題身上抽象出概念性的組件,然後在程序中將其表示爲一個對象。
  • 程序是一組對象,通過消息傳遞來告知彼此該做什麼。要請求調用一個對象的方法,你需要向該對象發送消息。
  • 每個對象都有自己的存儲空間,可容納其他對象。或者說,通過封裝現有對象,可製作出新型對象。所以,儘管對象的概念非常簡單,但在程序中卻可達到任意高的複雜程度。
  • 每個對象都有一種類型。根據語法,每個對象都是某個“類”的一個“實例”。其中,“類”(Class)是“類型”(Type)的同義詞。一個類最重要的特徵就是“能將什麼消息發給它?”。
  • 同一類所有對象都能接收相同的消息。這實際是別有含義的一種說法,大家不久便能理解。由於類型爲“圓”(Circle)的一個對象也屬於類型爲“形狀”(Shape)的一個對象,所以一個圓完全能接收發送給"形狀”的消息。這意味着可讓程序代碼統一指揮“形狀”,令其自動控制所有符合“形狀”描述的對象,其中自然包括“圓”。這一特性稱爲對象的“可替換性”,是OOP最重要的概念之一。

在解釋類的概念時,作者舉了一個燈泡的例子:

在這裏插入圖片描述

Light lt = new Light();
lt.on();

在這個例子中,類型/類的名稱是 Light,可向 Light 對象發出的請求包括打開 on、關閉 off、變得更明亮 brighten 或者變得更暗淡 dim。通過聲明一個引用,如 lt 和 new 關鍵字,我們創建了一個 Light 類型的對象,再用等號將其賦給引用。

爲了向對象發送消息,我們使用句點符號 . 將 lt 和消息名稱 on 連接起來。可以看出,使用一些預先定義好的類時,我們在程序裏採用的代碼是非常簡單直觀的。

緊接着,作者開始解釋多態的其他內容。

其中,對封裝的理解很重要。作者認爲,編程領域可以劃分爲研發和應用。專門做應用程序的程序員不應該關注基礎工具部分的代碼邏輯。當一個研發程序員作出一個工具類時,應該對內部的細節隱藏,而只開放需要讓使用者知道的部分。這個思想引出了Java的一個概念——訪問控制:

public(公開)表示任何人都可以訪問和使用該元素;

private(私有)除了類本身和類內部的方法,外界無法直接訪問該元素。private 是類和調用者之間的屏障。任何試圖訪問私有成員的行爲都會報編譯時錯誤;

protected(受保護)類似於 private,區別是子類(下一節就會引入繼承的概念)可以訪問 protected 的成員,但不能訪問 private 成員;

default(默認)如果你不使用前面的三者,默認就是 default 訪問權限。default 被稱爲包訪問,因爲該權限下的資源可以被同一包(庫組件)中其他類的成員訪問。

作者提到了組合和聚合的一些區別,這些理論很抽象。對比了英文原著及譯文,我認爲這兩者更多是語義、思維上的區別。兩者的代碼實現很可能看不出區別。兩個類生命週期不同步,則是聚合關係,生命週期同步就是組合關係。

繼承是一個很重要的點。利用繼承可以使問題的解決方式變爲“解決這一類問題”。如書中的例子:

在這裏插入圖片描述

垃圾回收機對垃圾進行分類。基類是“垃圾”。每塊垃圾都有重量、價值等特性,它們可以被切碎、熔化或分解。在此基礎上,可以通過添加額外的特性(瓶子有顏色,鋼罐有磁性)或行爲(鋁罐可以被壓碎)派生出更具體的垃圾類型。此外,一些行爲可以不同(紙張的價值取決於它的類型和狀態)。使用繼承,你將構建一個類型層次結構,來表示你試圖解決的某種類型的問題。第二個例子是常見的“形狀”例子,可能用於計算機輔助設計系統或遊戲模擬。基類是“形狀”,每個形狀都有大小、顏色、位置等等。每個形狀可以繪製、擦除、移動、着色等。由此,可以派生出(繼承出)具體類型的形狀——圓形、正方形、三角形等等——每個形狀可以具有附加的特徵和行爲

在派生中,"is-a"和"is-like-a"有區別。作者引出了一個問題:繼承應該只覆蓋基類的方法(不應該添加基類中沒有的方法)嗎。

如果答案是肯定的,那派生類和基類沒有什麼區別,且有相同的接口。可以用一個派生類對象完全替代基類對象,被稱作"替代原則"(也叫純粹替代),也就是“is-a”關係。作者認爲“is-a”關係是一種處理繼承的理想方式

與之相反的,如果在繼承過程中,派生類有了新的接口,與基類有了差異,那它就不算是完全替代,這就是“is-like-a”關係。

當你看到替代原則時,很容易會認爲純粹替代是唯一可行的方式,並且使用純粹替代的設計是很好的。但有些時候,你會發現必須得在派生(擴展)類中添加新方法(提供新的接口)。只要仔細審視,你可以很明顯地區分兩種設計方式的使用場合。

多態的實用性很強。完全掌握多態對程序員至關重要。理解了多態的實現原理,我們就會明白,面嚮對象語言都使用了後期綁定的概念。

當向對象發送信息時,被調用的代碼直到運行時才確定。編譯器確保方法存在,並對參數和返回值執行類型檢查,但是它不知道要執行的確切代碼。

作者舉了一個例子:

void doSomething(Shape shape) {
    shape.erase();
    // ...
    shape.draw();
}
    Circle circle = new Circle();
    Triangle triangle = new Triangle();
    Line line = new Line();
    doSomething(circle);
    doSomething(triangle);
    doSomething(line);

在這裏插入圖片描述

當預期接收 Shape 的方法被傳入了 Circle,會發生什麼。由於 Circle 也是一種 Shape,所以 doSomething(circle) 能正確地執行。也就是說,doSomething() 能接收任意發送給 Shape 的消息。這是完全安全和合乎邏輯的事情。

單繼承概念。作者對比了C++,在Java裏不允許像C++那樣有多個基類。

Java 的單繼承結構有很多好處。由於所有對象都具有一個公共接口,因此它們最終都屬於同一個基類。相反的,對於 C++ 所使用的多繼承的方案則是不保證所有的對象都屬於同一個基類。從向後兼容的角度看,多繼承的方案更符合 C 的模型,而且受限較少。

對於完全面向對象編程,我們必須要構建自己的層次結構,以提供與其他 OOP 語言同樣的便利。我們經常會使用到新的類庫和不兼容的接口。爲了整合它們而花費大氣力(有可能還要用上多繼承)以獲得 C++ 樣的“靈活性”值得嗎?如果從零開始,Java 這樣的替代方案會是更好的選擇。

而像Java這樣單繼承會有一個最大的好處——垃圾回收機制——會更好實現。原因很簡單,“由於運行期的類型信息會存在於所有對象中,所以我們永遠不會遇到判斷不了對象類型的情況”。

集合的概念。作者舉例了ArrayList 和LinkedList。兩者都是對List 的實現,但是由於底層的實現方式不一樣,導致了其有不同的使用場景。LinkedList 的查找更加快,而ArrayList 的插入效率更高。此外,集合與泛型的搭配,使得數據類型的存儲和處理變得方便。

最後部分,作者對比了Java與C++在對象生命週期上理念的不同。Java的方式致使垃圾回收機制成爲可能,而C++則必須以編程的方式銷燬對象。

Java 的內存管理是建立在垃圾收集器上的,它能自動發現對象不再被使用並釋放內存。垃圾收集器的存在帶來了極大的便利,它減少了我們之前必須要跟蹤的問題和編寫相關代碼的數量。因此,垃圾收集器提供了更高級別的保險,以防止潛在的內存泄漏問題,這個問題使得許多 C++ 項目沒落。

總結

前言簡介及第一章都是總領性的概念部分。沒有涉及語言的具體細節。大部分內容都是介紹Java的OOP特點,並對比了C++的區別。作者認爲,Java中的OOP雖然看起來要稍顯複雜,但是嚴格按照 Java 規範編寫的程序會比面向過程程序更容易被理解

面向對象編程的一個優點是:設計良好的 Java 程序代碼更容易被人閱讀理解。由於 Java 類庫的複用性,通常程序要寫的代碼也會少得多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章