1. 程式人生 > >「MoreThanJava」Day 7:介面詳解

「MoreThanJava」Day 7:介面詳解

![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage01/「MoreThanJava」Day7:介面/image-20200813144940633.png) - **「MoreThanJava」** 宣揚的是 **「學習,不止 CODE」**,本系列 Java 基礎教程是自己在結合各方面的知識之後,對 Java 基礎的一個總回顧,旨在 **「幫助新朋友快速高質量的學習」**。 - 當然 **不論新老朋友** 我相信您都可以 **從中獲益**。如果覺得 **「不錯」** 的朋友,歡迎 **「關注 + 留言 + 分享」**,文末有完整的獲取連結,您的支援是我前進的最大的動力! # Part 1. 介面概述 Java 是單繼承的。這意味著子類僅從一個父類繼承。通常,這就是你需要的。有時候多繼承會提供方便,但也會造成混亂,例如,當繼承的兩個父類具有不同版本的簽名相同的兩個方法時該呼叫哪一個呢? **介面為 Java 提供了多繼承的一些優點,而沒有缺點。** ## 介面的概念 在 Java 程式設計語言中,介面不是類,**而是對希望符合這個介面的類的一組需求。** 我們 [之前](https://www.wmyskxz.com/2020/08/07/morethanjava-day-5-mian-xiang-dui-xiang-jin-jie-ji-cheng-xiang-jie/) 接觸的 **抽象類**,性格偏內向,描述的是一組相對具體的特徵,比如某品牌特定型號的汽車,底盤架構、控制電路、剎車系統等是抽象出來的共同特徵,但根據動感型、舒適型、豪華型的區分,內飾、車頭燈、顯示屏等都可以存放不同版本的具體實現。 而 **介面** 是開放的,性格偏外向,它就像一份合同,定義了方法名、引數列表、返回值,甚至是丟擲異常的型別。誰都可以實現它,但如果想實現它的類就必須遵守這份介面約定的合同。 > 想一想比較熟悉的 USB 介面:它不僅僅約束了 U 盤 *(實現類)* 的大小和形狀,同樣也約束了電腦插槽 *(使用類)*。在程式設計中,介面類似。 ## 介面的定義 在 Java 中使用 `interface` 關鍵字來定義介面。介面是頂級的 "類",雖然關鍵字是 `interface`,但編譯之後的位元組碼副檔名還是 `.class`。一個典型介面的結構如下: ```java public interface InterfaceName { constant definitions method headers (without implementations). } ``` 比如,我們在 [前面文章](https://www.wmyskxz.com/2020/08/07/morethanjava-day-5-mian-xiang-dui-xiang-jin-jie-ji-cheng-xiang-jie/) 討論「為什麼不推薦使用繼承?」中舉的鳥類的例子,任何能飛的鳥都必須實現如下介面: ```java public interface Flyable { void fly(); } ``` 介面中的所有方法都自動是 `public`。因此,在介面中宣告方法時,不必提供關鍵字 `public`。*(在 Java 9 中允許了介面定義宣告為 `private` 的方法,在這之前都是不允許的..)* > 想一想介面就像是合同一樣,所以任何不清晰的細節都是不允許的。因此,介面中只允許明確的方法定義和常量出現。*(下方的例子中演示了一個不被允許的介面定義 —— 因為 `y` 變數沒有確定的值)* > > > ```java > interface ErrorInterfaceDefine { > public final int x = 32; > public double y; // No variables allowed > > public double addup(); > } > ``` 這看起來有點兒像類的定義,但沒有任何物件能夠構建一個介面 *(`new`一個介面.. 因為介面是絕對抽象的,不允許實現..)*,但你可以定義一個類實現 *(關鍵字 `impelents`)* 介面,一旦你這麼做了,你就可以構造這個 *(實現介面的)* 類的物件。 例如麻雀既能飛、也能叫、還能下蛋:*(實現多個介面使用 `,` 分隔)* ```java public class Sparrow impelents Flayable, Tweetable, EggLayable {//麻雀 //... 省略其他屬性和方法... @Override public void fly() { //... } @Override public void tweet() { //... } @Override public void layEgg() { //... } } ``` ## 介面的屬性 **❶** 介面不是類,不能使用 `new` 運算子例項化一個介面,但卻可以用來引用實現了這個介面的類物件: ```java Comparable x = new Employee(...); // OK provided Emloyee implements Comparable ``` **❷** 與建立類的繼承層次一樣,也可以擴充套件介面!比如,假設這裡有一個名為 `Moveable` 的介面: ```java public interface Moveable { void move(double x, double y); } ``` 然後,可以假設一個名為 `Powered` 的介面擴充套件了以上的 `Moveable` 介面: ```java public interface Powered extends Moveable { double milesPerGallon(); } ``` **❸** 雖然在介面中不能包含例項欄位,但是可以包含常量。比如: ```java public interface Powered extends Moveable { double SPEED_LIMIT = 95; // a public static final constant double milesPerGallon(); } ``` **❹** 另外有一些介面之定義了常量,而沒有定義方法。例如,標準庫中的 `SwingConstants` 就是這樣一個介面,其中只包含了 `NORTH`、`SOUTH` 和 `HORIZONTAL` 等常量。任何實現 `SwingConstants` 介面的類都自動地繼承了這些常量,並可以在方法中引用它們,而不必採用 `SwingConstants.NORTH` 這樣繁瑣的書寫形式。不過,這樣使用介面更像是退化,所以建議最好不要這樣使用... ![SwingConstants 原始碼部分截圖](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage01/「MoreThanJava」Day7:介面/image-20200812072319833.png) ➡️ **一個類只能有一個父類,但可以實現很多個介面**。這就為定義類的行為提供了極大的靈活性。*(我們之前也討論過——在討論繼承的章節——這裡不再贅述)* ## 靜態和私有方法 ➡️ 在 **Java 8** 中,允許在介面中增加靜態方法 *(允許不構建物件而直接使用的具體方法)*。理論上講,沒有任何理由認為這是不合法的,**只是這有違將介面作為抽象規範的初衷**。 目前為止,通常的做法都是將靜態方法放在 **伴隨類** *(可以理解為操作繼承介面的實用工具類)* 中。在標準庫中,你可以看到成對出現的介面和實用工具類,如 `Collection/ Collections` 或 `Path/ Paths`。 在 **Java 11** 中,`Path` 介面就提供了一個與之工具類 `Paths.get()` 等價的方法 *(該方法用於將一個 URI 或者字串序列構造成一個檔案或目錄的路徑)*: ```java public interface Path { public static Path of(String first, String... more) { ... } public static Path of(URI uri) { ... } } ``` 這樣一來,`Paths` 類就不再是必要的了。類似地,如果實現你自己的介面時,沒有理由再額外提供一個帶有實用方法的工具類。 ➡️ 另外,在 **Java 9** 中,介面中的方法可以是 `private`。`private` 方法可以是靜態方法或例項方法。由於私有方法只能在介面本身的方法中使用,所以它們的用法很有限,只能作為介面中其他方法的輔助方法。 ## 預設方法 在 **Java 8** 中,允許為介面方法提供一個預設的實現。必須用 `default` 修飾符標記這樣一個方法,例如 JDK 中的 `Iterator` 介面: ```java public interface Iterator { boolean hasNext(); E next(); default void remove() { throw new UnsupportedOperationExceition("remove"); } } ``` 這將非常有用!如果你要實現一個迭代器,就需要提供 `hasNext()` 和 `next()` 方法。這些方法沒有預設實現——它們依賴於你要遍歷訪問的資料結構。不過,如果你的迭代器是 **只讀** 的,那麼就不用操心實現 `remove()` 方法。 預設方法也可以呼叫其他方法,例如,我們可以改造 `Collection` 介面,定義一個方便的 `isEmpty()` 方法: ```java public interface Collection { int size(); // an abstract method default boolean isEmpty() { return size() == 0; } } ``` 這樣,實現 `Collection` 的程式設計師就不用再操心實現 `isEmpty()` 方法了。 *(事實上這也是 `AbstractCollection` 抽象類的定義——所有的集合具體實現幾乎都繼承了 `AbstractCollection`抽象類——但為什麼頂層的 `Collection` 介面不做這樣的修改呢?我起初是懷疑有一些特殊的集合為空的定義有特殊性,但我沒有找到..幾乎所有的集合為空判定都為自身的元素等於 `0`。所以答案是什麼呢?是解決預設方法衝突的 "類優先" 原則!