天天看點

碼出高效:Java開發手冊-第2章(3)

2.3 類

2.3.1 類的定義

類的定義由通路級别、類型、類名、是否抽象、是否靜态、泛型辨別、繼承或實作關鍵字、父類或接口名稱等組成。類的通路級别有public 和無通路控制符,類型分為class、interface、enum。

Java 類主要由兩部分組成:成員和方法。在定義Java 類時,推薦首先定義變量,然後定義方法。由于公有方法是類的調用者和維護者最關心的方法,是以最好首屏展示;保護方法雖然隻被子類關心,但也可能是模闆設計模式下的核心方法,是以重要性僅次于公有方法;而私有方法對外部來說是一個黑盒實作,是以一般不需要被特别關注;最後是getter/setter 方法,雖然它們也是公有方法,但是因為承載的資訊價值較低,一般不包含業務邏輯,是以所有getter/setter 方法須放在類最後。

2.3.2 接口與抽象類

正如面向對象四大特性(抽象、封裝、繼承、多态)所述,定義類的過程就是抽象和封裝的過程,而接口與抽象類則是對實體類進行更高層次的抽象,僅定義公共行為和特征。接口與抽象類的共同點是都不能被執行個體化,但可以定義引用變量指向執行個體對象。本節主要分析兩者的不同之處,首先從文法上進行區分,如表2-1 所示。

表2-1 接口與抽象類的文法差別

碼出高效:Java開發手冊-第2章(3)

抽象類在被繼承時展現的是is-a 關系,接口在被實作時展現的是can-do 關系。與接口相比,抽象類通常是對同類事物相對具體的抽象,通常包含抽象方法、實體方法、屬性變量。如果一個抽象類隻有一個抽象方法,那麼它就等同于一個接口。is-a關系需要符合裡氏代換原則,例如Eagle is a Bird. Bird is an Object。 can-do 關系要符合接口隔離原則,實作類要有能力去實作并執行接口中定義的行為,例如Plane can fly. Bird can fly. 中應該把fly 定義成一個接口,而不是把fly() 放在某個抽象類中,再由Plane 和Bird 利用is-a 關系去繼承此抽象類。因為嚴格意義上講,除fly 這個行為外,在Plane 和Bird 之間很難找到其他共同特征。

抽象類是模闆式設計,而接口是契約式設計。抽象類包含一組相對具體的特征,性格偏内向,比如某品牌特定型号的汽車,底盤架構、控制電路、刹車系統等是抽象出來的共同特征,但根據動感型、舒适型、豪華型的區分,内飾、車頭燈、顯示屏等可以存在不同版本的實作。接口是開放的,性格偏外向,它就像一份合同,定義了方法名、參數、傳回值,甚至抛出異常的類型。誰都可以來實作它,但如果想實作它的類就必須遵守這份接口約定合同,比如,任何類型的車輛都必須實作如下接口:

public interface VehicleSafe {

/**

* @param initSpeed 刹車時的初始速度

* @param brakeTime 從initSpeed 開始刹車到停止行駛的時間,機關是毫秒

* @return 從initSpeed 開始刹車到停止行駛的距離

*/

double brake(int initSpeed, int brakeTime);

}

刹車是一個開放式的強制行為規範,任何車輛都必須具有刹車的能力,要明确在特定初速度的情況下,刹車時間多久,刹車距離多長。此規範對任何車輛都是強限制的,這就是契約。

接口是頂級的“類”,雖然關鍵字是interface,但是編譯之後的位元組碼擴充名還是.class。抽象類是二當家,接口位于頂層,而抽象類對各個接口進行了組合,然後實作部分接口行為,其中AbstractCollection 是最典型的抽象類:

public abstract class AbstractCollection<E> implements Collection<E> {

// Collection 定義的抽象方法,但本類沒有實作

// Collection 接口定義的方法,size() 這個方法對于連結清單和順序表有不同的實作方式

public abstract int size();

// 實作Collection 接口的這個方法,因為對AbstractCollection 的子類

// 它們判空的方式是一緻的,這就是模闆式設計,對于所有它的子類,實作共同的方法體,

// 通過多态調用到子類的具體size() 實作

public boolean isEmpty() {

// 實作Collection 的方法

return size() == 0;

}

// 其他屬性和部分方法實作……

}

Java 語言中類的繼承采用單繼承形式,避免繼承泛濫、菱形繼承、循環繼承,甚至“四不像”實作類的出現。在JVM 中,一個類如果有多個直接父類,那麼方法的綁定機制會變得非常複雜。接口繼承接口,關鍵字是extends,而不是implements,允許多重繼承,是因為接口有契約式的行為約定,沒有任何具體實作和屬性,某個實體類在實作多重繼承後的接口時,隻是說明“can do many things”。當糾結定義接口還是抽象類時,優先推薦定義為接口,遵循接口隔離原則,按某個次元劃分成多個接口,然後再用抽象類去implements 某些接口,這樣做可友善後續的擴充和重構。