《Java程式設計思想》讀書筆記-物件導論
計算機是頭腦延伸的工具,是一種不同型別的表達媒體。本文以背景性的和補充性的材料,介紹包括開發方法概述在內的面向物件程式設計(Object-oriented Programming,OOP)的基本概念。
本文通過概念+程式碼的方式,來幫助讀者瞭解面向物件程式設計的全貌。
抽象過程
概念
機器模型:位於 解空間
內,是對問題建模的地方;可以這樣理解,組合語言和命令式語言,在解決問題時要基於計算機的架構;因此架構限定了解決方案,所以說機器模型是解空間。
實際待解決問題: 問題空間
,是問題存在的地方
如何抽象
抽象的型別和質量,決定了人們所能夠解決的問題的複雜性。抽象的型別指的是“所抽象的是什麼”。一種是在機器模型和實際待解決問題的模型之間建立聯絡的抽象;另一種是隻針對待解決問題建模。而面向物件則是向程式設計師提供表示問題空間中元素的工具,我們將問題空間中的元素及其在解空間中的表示稱為“物件”。
面向物件程式設計的特性:
- 萬物皆是物件;
- 程式是物件的集合,物件間方法的呼叫是程式執行的基本表現;
- 物件可以包含其他物件;
- 每個物件都擁有其特定的型別;
- 某一特定型別的所有物件都可以接收同樣的方法呼叫;
什麼是物件?物件具有狀態、行為和標識。每一個物件都可以擁有內部資料和方法,並且可以唯一的與其他物件區分開來
每個物件都應該都歸屬於一個類或介面
名詞解釋
物件:具有狀態、行為和標識的實體。如銀行存款賬戶是一個類,那麼具體的每個人的銀行存款賬戶就是這個類目下的物件。
類:可以看作型別來考慮。比如說鳥類,是動物中的其中一種型別。
類和物件
所有的物件都是唯一的,但同時具有相同的特性和行為的物件也都歸屬於某個特定的類。
類在Java中用關鍵詞class表示。每個類的物件都具有某種共性和個性,如銀行存款賬戶,每個賬戶中都有餘額的屬性,但每個賬戶中的餘額又不同。在實際中,面向物件程式設計語言都用class關鍵字來表示資料型別,換而言之,每一個類都是一個數據型別。程式設計師可以自由地新增新的類(資料型別)來擴充套件程式語言,對實際問題進行處理。
物件的獲取與方法呼叫
面向物件的挑戰之一,就是在問題空間的元素和解空間的物件之間建立一對一的對映。
獲取有用物件,必須以某種方式對物件進行請求,使物件完成各種任務。型別決定介面,而介面決定物件能滿足的請求。就比如鳥型別,其提供的介面有飛翔,因此其能滿足飛翔的請求。在介面確定了某一特定物件能夠發出的請求後,介面的實現掌控著請求的具體行為的展現方式。在型別中,每一個可能的請求都有一個方法與之關聯,當向物件傳送請求時,與之關聯的方法就會被呼叫。
一下程式碼是獲取一個物件並呼叫其中的方法例項(你可以暫時不用理解,只需要知道形式即可,後面再反過來看就好)
public class Light { //開燈 public void on() { System.out.println("Light is on!"); } //關燈 public void off() { System.out.println("Light is off!"); } //這裡是獲取一個物件並呼叫其中方法的例項 public static void main(String [] args) { //這裡是核心程式碼,開燈操作 Light lt = new Light(); lt.on(); } }
物件是服務提供者
程式通過呼叫其他物件提供的服務來向用戶提供服務。程式設計師的目標就是去建立(或者最好是從現有的程式碼庫中尋找)能夠提供解決問題所需服務的一系列物件。
為什麼要把物件看作是服務提供者呢?
-
這是將問題分解為物件集合的一種合理方式。比如說,你正在建立一個簿記系統,那麼,這個系統可以拆分為:我需要一個包括了預定義的簿記輸入螢幕的物件、一個執行簿記計算的物件集合以及一個處理在不同的印表機上列印支票和開發票的物件。
-
它有助於提高物件的內聚性。就像上面所定義的簿記系統,每個物件都可以很好地完成一項任務,但是它並不試圖做更多的事情。職能太多,可能會導致物件的內聚性降低。簡而言之,每個物件只做它該做的事。
程式訪問許可權控制
將程式開發人員按照角色劃分為 類建立者
和 客戶端程式設計師
。類建立者建立新的資料型別,而客戶端程式設計師在其應用中使用類建立者建立的新資料型別。如此一來,客戶端程式設計師的主要目的就是收集各種用來實現快速應用開發的類;而類建立者的目的則是構建類,並向客戶端程式設計師暴露必須的部分。
為什麼類建立者需要對類的某些部分進行隱藏呢?或者說,為什麼需要進行訪問許可權控制呢?
-
讓客戶端程式設計師無法觸及他們不該觸及的部分,讓客戶端程式設計師分清楚,哪些東西對他們來說是必須的,哪些是可以忽略的。
-
允許庫設計者可以改變類內部的工作方式而不用擔心會影響到客戶端程式設計師。因為對客戶端程式設計師所提供的那一部分可見的內容總是不變的,而庫設計者改變的是其中隱藏的部分。
Java的訪問許可權控制關鍵字
修飾符 | 類內部 | 同包 | 子類 | 任何地方 |
---|---|---|---|---|
private | √ | × | × | × |
無 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
訪問許可權關鍵字的修飾範圍
- public:可以修飾外部類、屬性、方法;
- protected:只能修飾屬性和方法;
- private:只能修飾屬性、方法、內部類;
複用具體實現
程式碼複用是面向物件程式設計語言所提供的最了不起的優點之一。
程式碼複用的基本方式:
- 直接使用該類的一個物件;
- 可以將某個類的一個物件置於一個新類中,作為新類的成員出現;
類之家的關係
關係是指事物之間存在單向或者相互的作用力或者影響力的狀態。
在兩個類之間存在有關係和沒關係兩種情況,在有關係的情況下,其關係包括以下六種型別
類關係 | 英文名 | 描述 | 強權方 | UML圖表示 | 示例說明 |
---|---|---|---|---|---|
繼承 | extends | 父類與子類之間的關係:is-a | 父類 | 空心三角+實線,空心三角指向父類 | 鳥是動物 |
實現 | implements | 介面與實現類之間的關係:can-do | 介面 | 空心三角+虛線,空心三角指向介面 | 鳥實現了飛翔的介面 |
組合 | composition | 比聚合更強的關係:contains-a | 整體 | 實心菱形+實線,實心菱形指向整體 | 人類的頭和身體是強組合關係 |
聚合 | aggregation | 暫時組裝的關係:has-a | 組裝方 | 空心菱形+實線,空心菱形指向組裝方 | 狗和牽狗的繩子是聚合關係 |
依賴 | dependency | 一個類依賴於另一個類:depends-a | 被依賴方 | 箭頭+虛線,箭頭指向被依賴方 | 人喂小狗,小狗是喂這個動作的被依賴方 |
關聯 | association | 類與類之間存在互相平等的使用關係:links-a | 平等 | 實線 | 人與信用卡的關係,人用信用卡,信用卡可以讀取個人資訊 |
繼承關係
以現有類為基礎,複製它,然後通過新增和修改這個副本來建立新類。當源類發生變化時,被修改的副本也會反應出這種變動。生物學中對科目的定義,用於解釋繼承關係再恰當不過。
相關名詞定義
父類:又稱源類、基類、超類;
子類:又稱匯出類、繼承類;
父類和子類
父類和子類之間的型別層次結構同時體現了他們之間的相似性和差異性。當繼承現有型別時,也就創造了新的型別,同時子類又歸屬於父類的型別。這個新的型別不僅包括現有型別的所有成員,而且更重要的是它複製了父類的介面,這意味著所有對父類物件的呼叫同時可可以對子類物件發起,這遵循了程式設計原則之一的里氏替換原則。
如果只是簡單地繼承一個類而不做其他任何事情,那麼在父類介面中的方法將會直接繼承到子類中。當需要使父類和子類產生差異時,有以下兩種方式:
- 直接在子類中新增新的方法;在採用該種方案時需要仔細考慮是否存在父類也需要這些額外方法的可能性。
- 覆寫父類中的某個方法;該種方案需要在子類中定義與父類需覆寫方法同名、同返回值型別、同方法引數型別的方法。
那麼繼承是否應該只覆寫父類的方法呢?
如果繼承只覆寫了父類的方法,那麼子類物件可以完全替代父類物件,這通常稱之為 替代原則
,在這種情況下的類關係稱為 is-a
;但有時又的確需要在子類中新增新的介面,這種情況下父類無法訪問新新增的介面,這種情況下類關係為 is-like-a
,這時這種父類與子類之間的關係,被視為 非存粹替代
多型
在處理類層次關係的時候,如果把任意一個特定型別的物件可以當作其基類物件來對待,就使得人們可以編寫出不依賴於特定型別的程式碼。
相關概念
前期繫結:編譯器將產生對一個具體函式名字的呼叫,而在執行時需要將這個呼叫解析到將要被執行的程式碼的絕對地址(意味著執行前就需要知道具體程式碼的位置)。
後期繫結:編譯器只確保呼叫的方法存在,而且呼叫引數和返回值型別正確;在執行時,通過特殊程式碼,解析具體將要執行的程式碼的具體位置。
多型的實現理念
通過匯出新的子類而輕鬆擴充套件設計的能力,是對改動進行封裝的基本方式之一。
在試圖將子類物件當作其基類物件來看待時,需要解決的一個問題是:編譯器無法精確地瞭解哪一段程式碼將會被執行。在OOP程式設計中,程式直到執行時才能夠確定程式碼的位置。
OOP程式設計語言使用了後期繫結的概念:編譯器確保呼叫方法的存在,並對呼叫引數和返回值執行型別檢查,但並不知道將被執行的確切程式碼。Java使用一小段特殊的程式碼來替代絕對地址呼叫,這段 特殊程式碼
用來計算方法體的具體位置。Java預設是動態繫結的。
向上轉型
把子類物件看作父類物件的過程,稱作 向上轉型
。原因是在類圖中,父類總是位於類圖的頂部,把子類物件視為父類物件,即將子類型別向上推導。
單根繼承
Java中所有的類最終都繼承自單一的基類:Object
單根繼承結構保證所有物件都具備某些功能。Object是任何類的預設父類,是在哲學方向上繼續寧的延伸思考。
- 我是誰? getClass()說明本質上是誰,而toString()是當前類的名片。
- 我從哪裡來? Object()構造方法是生產物件的基本方式;clone()是繁殖物件的另一種方式。
- 我到哪裡去? finalize()方法說明了物件的最終歸屬
- 我是否是獨一無二的? hashCode()和equals()就是判斷與其他元素是否相同的一組方法。
- 與其他人如何協調? wait()和notify()方法是物件間通訊和協作的一組方法。
容器
通常來說,如果不知道在解決某個特定問題時需要多少物件,或者他們將存活多久,那麼就不可能知道如何儲存這些物件。
在Java標準類庫中提供了大量容器。不同的容器提供了不同型別的介面和外部行為,同時對某些操作具有不同的效率。如List中的ArrayList和LinkedList由於底層實現的不同,具備不同的應用場景。
引數化型別
由於容器只儲存Object,所以將物件引入置入容器時,被向上轉型為Object,在取出型別時會丟失其型別。在一定程度上可以使用向下轉型的方式來獲取其實際型別,但是這樣做存在風險。
package a; import java.util.*; public class Container { public static void main(String [] args) { List list = new ArrayList(); list.add("Hello World!"); list.add(1); //程式執行到這裡是不會報錯的,但是執行下面這一步的時候,就會出現異常了 for(Object o : list) { //這一步會出現異常,因為List中存放的不僅僅是String型別,還有Integer型別,向下轉型出現異常 String a = (String) o; System.out.println(a); } } }
那麼用什麼方式使容器記住這些物件究竟使什麼型別呢?解決方案稱為引數化型別,在Java中也稱為泛型。表示方法為一對尖括號,中間包含型別資訊。
List<String> list = new ArrayList<String>();
這樣一來,就限定了List中只能存放String型別的物件啦!當然,我們還是能夠通過反射繞過這層驗證,畢竟在編譯後執行時,是去泛型的。
物件的建立和生命週期
在使用物件時,最關鍵的問題之一便是他們的生成和銷燬方式。
物件的建立
new Constructor();:通過new關鍵詞向堆中申請記憶體,通過Constructor來說明類的建立方式。
生命週期
Java採用 動態記憶體分配
的方式。動態方式有個一般性假設:物件趨於變得複雜,所以查詢和釋放記憶體空間的開銷不會對物件的建立造成重大沖擊。動態方式所帶來的更大的靈活性是解決一般化程式設計問題的要點。
Java提供了被稱為 垃圾收集器
的機制,用來處理記憶體釋放問題。垃圾收集器的執行基礎是 單根繼承結構
和 只能在堆上建立物件
的特性。
異常處理
錯誤處理始終是程式設計的難題之一。
什麼是異常
異常是一種物件,其從出錯點被 丟擲
,並被特定型別的異常處理器所 捕獲
。異常處理就像與程式正常執行路徑並行的、在錯誤發生時執行的另一條路徑。
異常的處理
異常不能被忽略,它保證一定會在某處得到處理。異常提供了一種從錯誤狀態進行可靠恢復的途徑。Java一開始就內建了異常處理,並強制你必須使用它。它是唯一可接受的錯誤報告方式。
併發程式設計
在計算機程式設計中,存在著在同一時刻處理多個任務的思想。這些彼此獨立執行的部分稱為執行緒,同一時刻處理多個任務稱為併發。
在單一處理器中,執行緒只是一種為單一處理器分配執行時間的手段,換而言之,如果只有一個處理器,那麼多執行緒程式的執行不過是多個任務競爭使用處理器的效能。在多處理器的情況下,實現的才是真正意義上的併發,多處理器平行計算。
多執行緒同時存在一個隱患,在存在共享資源的時候,可能會造成資源之間的競爭,進而造成死鎖。所以在多執行緒修改共享資源時,必然在共享資源使用期進行鎖定。
在Java中,JDK1.5後提供了concurrent包支援更好的併發特性。
結語
以上是物件導論的一些基本概念,是繼續閱讀後面章節的非必要補充性材料。對於文中的一些程式碼段或概念,暫時不理解的,可以先放一放,等後面看完了,再回過來看就恍然大悟了。
下一節將講解物件並寫第一個Java程式。歡迎關注我的微信公眾號,可以更方便的獲取每日推送