天天看點

Java程式設計思想精粹(On Java8)(十)-接口(上)1 抽象類2 接口

接口和抽象類提供了一種将接口與實作分離的更加結構化的方法。

這種機制在程式設計語言中并不常見

C++ 隻對這種概念提供間接支援

Java 為它們提供了直接支援 - 關鍵字

盡管你的第一想法是建立接口,但對于建構具有屬性和未實作方法的類來說,抽象類也是重要且必要的工具。

你不可能總是使用純粹的接口。

1 抽象類

建立通用接口的唯一理由是,不同的子類可以用不同的方式表示此接口。通用接口建立了一個基本形式,以此表達所有派生類的共同部分。

有時把 Instrument 稱為抽象基類,或簡稱抽象類。

對于抽象類,它的對象幾乎總是沒有意義。建立一個抽象類是為了通過通用接口操縱一系列類。是以隻是表示接口,不是具體實作,是以建立一個抽象類的對象毫無意義,我們可能希望阻止使用者這麼做。通過讓抽象類所有的方法産生錯誤,就可以達到這個目的,但是這麼做會延遲到運作時才能得知錯誤資訊,并且需要使用者進行可靠、詳盡的測試。最好能在編譯時捕捉問題。

1.1 抽象方法

Java 提供了一個叫做抽象方法的機制,這個方法是不完整的:它隻有聲明沒有方法體。包含抽象方法的類叫做抽象類。如果一個類包含一個或多個抽象方法,那麼類本身也必須限定為抽象的,否則,編譯器會報錯。如果一個抽象類是不完整的,當試圖建立這個類的對象時,Java 會怎麼做呢?它不會建立抽象類的對象,是以我們隻會得到編譯器的錯誤資訊。這樣保證了抽象類的純粹性,我們不用擔心誤用它。

如果建立一個繼承抽象類的新類并為之建立對象,那麼就必須為基類的所有抽象方法提供方法定義。如果不這麼做(可以選擇不做),新類仍然是一個抽象類,編譯器會強制我們為新類加上 abstract 關鍵字。

可以将一個不包含任何抽象方法的類指明為 abstract,在類中的抽象方法沒啥意義但想阻止建立類的對象時,這麼做就很有用。

為了建立可初始化的類,就要繼承抽象類,并提供所有抽象方法的定義。留意 @Override 的使用。沒有這個注解的話,如果你沒有定義相同的方法名或簽名,抽象機制會認為你沒有實作抽象方法進而産生編譯時錯誤。是以,你可能認為這裡的 @Override 是多餘的。但是,@Override 還提示了這個方法被覆寫——我認為這是有用的,是以我會使用 @Override,即使在沒有這個注解,編譯器告訴我錯誤的時候。

接口隻允許 public 方法,即使不加通路修飾符。然而,抽象類啥都允許。

private abstract 被禁止了是有意義的,因為你不可能在 AbstractAccess 的任何子類中合法地定義它。

建立抽象類和抽象方法是有意義的,因為它們使得類的抽象性很明确,并能告知使用者和編譯器使用意圖。

抽象類同時也是一種有用的重構工具,使用它們使得我們很容易地将沿着繼承層級結構上移公共方法。

2 接口

使用 interface 關鍵字建立接口。描述 Java 8 之前的接口更加容易,因為它們隻允許抽象方法。

  • 我們甚至不用為方法加上 abstract 關鍵字
  • Java程式設計思想精粹(On Java8)(十)-接口(上)1 抽象類2 接口
  • 因為方法在接口中,Java 知道這些方法不能有方法體(仍然可以為方法加上 abstract 關鍵字,但是看起來像是不明白接口的小白)。

是以,在 Java 8之前我們可以這麼說:interface 關鍵字産生一個完全抽象的類,沒有提供任何實作。我們隻能描述類應該像什麼,做什麼,但不能描述怎麼做,即隻能決定方法名、參數清單和傳回類型,但是無法确定方法體。接口隻提供形式,通常來說沒有實作,盡管在某些受限制的情況下可以有實作。

一個接口表示:所有實作了該接口的類看起來都像這樣。是以,任何使用某特定接口的代碼都知道可以調用該接口的哪些方法,而且僅需知道這些。是以,接口被用來建立類之間的協定。

Java 8 中接口稍微有些變化,因為 Java 8 允許接口包含預設方法和靜态方法——基于某些重要原因,看到後面你會了解。

接口的基本概念仍然沒變,介于類型之上、實作之下。

接口與抽象類最明顯的差別可能就是使用上的慣用方式。

接口的典型使用是代表一個類的類型或一個形容詞,如 Runnable 或 Serializable

而抽象類通常是類層次結構的一部分或一件事物的類型

和類一樣,需要在關鍵字 interface 前加上 public 關鍵字(但隻是在接口名與檔案名相同的情況下),否則接口隻有包通路權限,隻能在接口相同的包下才能使用它。

Java程式設計思想精粹(On Java8)(十)-接口(上)1 抽象類2 接口

接口同樣可以包含屬性,這些屬性被隐式指明為 static 和 final。這就很妙了,尤其适合存儲常量,避免了一大堆重複的 public static final 修飾。

Java程式設計思想精粹(On Java8)(十)-接口(上)1 抽象類2 接口

讓一個類遵循某個特定接口(或一組接口)使用 implements 關鍵字,它表示:接口就像它看起來一樣,現在我要說明它是如何工作的。除此之外,它看起來像繼承。

可以顯式地聲明接口中的方法為 public,但是即使你不這麼做,它們也是 public 的。是以當實作一個接口時,來自接口中的方法必須被定義為 public。否則,它們隻有包通路權限,這樣在繼承時,它們的可通路權限就被降低了,這是 Java 編譯器所不允許的。

2.1 預設方法

關鍵字 default 允許在接口中提供方法實作——在 Java 8 之前被禁止。當在接口中使用它時,任何實作接口卻沒有定義方法的時候可以使用 default 建立的方法體。

在 Java 8 之前,這些定義要在每個實作中重複實作,顯得多餘且令人煩惱。

預設方法比抽象類中的方法受到更多的限制,但是非常有用。

如果我們在接口中增加一個新方法 newMethod(),而在實作類中沒有實作它,編譯器就會報錯。如果我們使用關鍵字 default 為 newMethod() 方法提供預設的實作,那麼所有與接口有關的代碼能正常工作,不受影響,而且這些代碼還可以調用新的方法 newMethod()。

增加預設方法的極具說服力的理由是它允許在不破壞已使用接口的代碼的情況下,在接口中增加新的方法。預設方法有時也被稱為守衛方法或虛拟擴充方法。

1.2 多繼承

一個類可能從多個父類型中繼承特征和特性。

Java 在設計之初,C++ 的多繼承機制飽受诟病。Java 過去是一種嚴格要求單繼承的語言:隻能繼承自一個類(或抽象類),但可以實作任意多個接口。在 Java 8 之前,接口沒有包袱——它隻是方法外貌的描述。

現在,Java 通過預設方法具有了某種多繼承的特性。結合帶有預設方法的接口意味着結合了多個基類中的行為。因為接口中仍然不允許存在屬性(隻有靜态屬性),是以屬性仍然隻會來自單個基類或抽象類,即不會存在狀态的多繼承。

當兩種接口中有相同簽名的方法,子類實作他倆時,需要覆寫沖突的方法:可以重定義 jim() 方法,也能使用 super 關鍵字選擇基類實作中的一種。

1.3 靜态方法

Java 8 允許在接口中添加靜态方法。這麼做能恰當地把工具功能置于接口中,進而操作接口,或者成為通用的工具。

這是模版方法設計模式的一個版本,一個模版方法。