如何理解 Java 中接口存在的意義

0. 前言

在我本身早期學習編程的時候,對接口存在的意義實在困惑,我本身亂寫代碼的時候基本上不可能意識到須要去寫接口,不知道接口到底有什麼用,爲何要定義接口,感受定義接口只是 提早作了個多餘的工做。git

這裏我先拋出一個形象的解釋,你們帶着這個解釋結合全文來理解接口存在的意義是什麼:程序員

咱們把電腦主板上的內存插槽,顯卡插槽等類比爲接口,爲何在主板上搞這麼多插槽呢?多浪費機箱空間啊?直接用電烙鐵把顯卡和內存的引腳一根一根焊到主板上不就得了(手動滑稽)。估計讀到這裏大夥兒內心也大概明白了接口的大體做用,焊死了後,若是你焊錯位置了或者拆電腦的時候,就須要使用電烙鐵進行拆裝,多愚蠢哦。web

全文脈絡思惟導圖以下:面試

1. 什麼是抽象類

在講解接口以前,抽象類是繞不過去的一個概念,接口能夠認爲是一個比抽象類還要抽象的類。算法

什麼是抽象類?「包含一個或多個抽象方法的類就是抽象類,抽象方法即沒有方法體的方法」,抽象方法和抽象類都必須聲明爲 abstract。例如:數據庫

// 抽象類
public abstract class Person {
    // 抽象方法
 public abstract String getDescription();
}

切記!「除了抽象方法以外,抽象類還能夠包含具體數據和具體方法」。例如, 抽象類 Person 還保存着姓名和一個返回姓名的具體方法:編程

public abstract class Person{
    private String name;
    public Person(String name){
     this.name = name ;
    }
    public abstract String getDescription();
    public String getName(){
     return name;
    }
}

許多程序員都會「錯誤」的認爲,在抽象類中不能包含具體方法。其實這也是接口和抽象類的不一樣之處,接口中是不能包含具體方法的。設計模式

「抽象類不能被實例化」。也就是說,若是將一個類聲明爲 abstract, 就不能建立這個類的對象。安全

new Person("Jack"); // Error

能夠定義一個抽象類的對象變量, 可是它只能引用非抽象子類的對象。假設 Student 類是 Person 的非抽象子類:微信

Person p = new Student("Jack"); // Right

所謂非抽象子類就是說,若是建立一個繼承抽象類的子類併爲之建立對象,那麼就「必須爲父類的全部抽象方法提供方法定義」。若是不這麼作(能夠選擇不作),子類仍然是一個抽象類,編譯器會強制咱們爲新類加上 abstract 關鍵字。

下面定義擴展抽象類 Person 的具體子類 Student

public class Student extends Person 
    private String major; 
    public Student(String name, String major) 
        super(name); 
        this.major = major; 
    } 
    @Override
    public String getDescription()// 實現父類抽象方法
     return "a student majoring in " + major; 
    } 

Student 類中實現了父類中的抽象方法 getDescription 。所以,「在 Student類中的所有方法都是非抽象的, 這個類再也不是抽象類」

👇 調用以下:

Person p = new Student("Jack","Computer Science");
p.getDescription();

因爲不能構造抽象類 Person的對象, 因此變量 p 永遠不會引用 Person 對象, 而是引用諸如 Student這樣的具體子類對象, 而這些對象中都重寫了 getDescription方法。

2. 什麼是接口

接口的本質其實也是一個類,並且是一個比抽象類還要抽象的類。怎麼說呢?抽象類是可以包含具體方法的,而接口杜絕了這個可能性,「在 Java 8 以前,接口很是純粹,只能包含抽象方法,也就是沒有方法體的方法」。而 Java 8 中接口出現了些許的變化,開始容許接口包含默認方法和靜態方法,這個下文會講解。

Java 使用關鍵字 interface 而不是 class 來建立接口。和類同樣,一般咱們會在關鍵字 interface 前加上 public 關鍵字,不然接口只有包訪問權限,只能在接口相同的包下才能使用它。

public interface Concept {
    void idea1();
    void idea2();
}

一樣的,接口中既然存在抽象方法,那麼他就須要被擴展(繼承)。使用 implements 關鍵字使一個類擴展某個特定接口(或一組接口),通俗來講:接口只是外形,如今這個擴展子類要說明它是如何工做的。

class Implementation implements Concept {
    @Override
    public void idea1() {
        System.out.println("idea1");
    }
    
    @Override
    public void idea2() {
        System.out.println("idea2");
    }
}

這裏須要注意的是,你能夠選擇顯式地聲明接口中的方法爲 public,可是「即便你不這麼作,它們也是 public 的」。因此當實現一個接口時,來自接口中的方法必須被定義爲 public。不然,它們只有包訪問權限,這樣在被繼承時,它們的可訪問權限就被下降了,這是 Java 編譯器所不容許的。

另外,接口中是容許出現常量的,與接口中的方法都自動地被設置爲 public—樣,「接口中的域將被自動被設置爲 public static final 類型」,例如:

public interface Concept {
 void idea1()// public void idea1();
    // 靜態屬性
 double item = 95// a public static final constant
}

能夠將接口方法標記爲 public,將域標記爲 public static final。有些程序員出於習慣或提升清晰度的考慮, 願意這樣作。但 Java 語言規範卻「建議不要書寫這些多餘的關鍵字」

3. 接口的特性

接口和類其中不一樣的一點就是,咱們「沒法像類同樣使用 new 運算符來實例化一個接口」

x = new Concept(. . .); // ERROR

緣由也很簡單,接口連具體的構造方法都沒有,確定是沒法實例化的。

固然, 儘管不能構造接口的對象,聲明接口的變量仍是能夠的:

Concept x; // OK

接口變量必須引用實現了接口的類對象:

x = new Implementation(. . .); // OK provided Implementation implements Concept

接下來, 如同使用 instanceof 檢查一個對象是否屬於某個特定類同樣, 也可使用 instanceof檢查一個對象是否實現了某個特定的接口:

if(x instanceof Concept){
 ...
}

另外,與能夠創建類的繼承關係同樣,「接口也能夠被繼承」

public interface Concept1 {
    void idea1();
    void idea2();
}

-------------------------------------------
    
public interface Concept2 extends Concept1{
 double idea3();
}

固然,讀到這裏你們可能依然沒法理解,既然有了抽象類,爲何 Java 程序設計語言還要任勞任怨地引入接口這個概念?

很重磅!由於「一個類能夠實現多個接口,可是一個類只能繼承一個父類」。正是接口的出現打破了 Java 這種單繼承的侷限,爲定義類的行爲提供了極大的靈活性。

class Implementation implements Concept1Concept2 // OK

有一條實際經驗:在合理的範圍內儘量地抽象。顯然,接口比抽象類還要抽象。所以,通常更傾向使用接口而不是抽象類。

4. Java 8 接口新特性

上文提過一嘴,「在 Java 8 中,容許在接口中增長靜態方法和默認方法」。理論上講,沒有任何理由認爲這是不合法的,只是這有違於將接口做爲抽象規範的初衷。舉個例子:

public interface Concept {
    // 靜態方法
 public static void get(String name){
     System.out.println("hello " + name);
    }
    // 默認方法
    default void idea1(){
        System.out.println("this is idea1");
    };
}

default 修飾符標記的方法就是默認方法,這樣子類就不須要去實現這個方法了。

不過,引入默認方法後,就出現了一個「默認方法衝突」的問題。若是先在一個接口 A 中將一個方法 idea 定義爲默認方法, 而後又在另外一個接口 B 或者超類 C 中定義了一樣的方法  idea,而後類 D 實現了這兩個接口 A 和 B(或超類 C)。因而類 D 中就有了方法 idea 的兩個默認實現,出現了衝突,爲此,Java 制定了一套規則來解決這個二義性問題:

1 )  「超類優先」。若是超類提供了一個具體方法,接口中的同名且有相同參數類型的默認方法會被忽略。

2 )  「接口衝突」。若是一個父類接口提供了一個默認方法,另外一個父類接口也提供了一個同名並且參數類型相同的方法,子類必須覆蓋這個方法來解決衝突。例如:

interface A {
 default void idea(){
  System.out.println("this is A");
 }
}

interface B {
 default void idea(){
  System.out.println("this is B");
 }
}

// 須要在 D 類中覆蓋 idea 方法
class D implements AB{
    public void getName(){
     System.out.println("this is D");
    }
}

如今假設 B接口沒有爲 idea 提供默認實現:

interface B {
 void idea();
}

那麼 D 類會直接從 A 接口繼承默認方法嗎?這好像挺有道理, 不過,Java 設計者更強調一致性。兩個接口如何衝突並不重要,「只要有一個接口提供了一個默認實現,編譯器就會報告錯誤, 咱們就必須解決這個二義性」

固然,若是兩個接口都沒有爲共享方法提供默認實現, 那麼就與 Java 8 以前的狀況同樣,這裏不存在衝突。

5. 接口存在的意義

在我本身早期學習編程的時候,對接口存在的意義實在困惑,我本身亂寫代碼的時候基本上不可能意識到須要去寫接口,不知道接口到底有什麼用,爲何要定義接口,感受定義接口只是提早作了個多餘的工做。

其實不是,定義接口並不是多餘,「接口是用來提供公用的方法,規定子類的行爲的」。舉個例子,讓你們直觀的感覺下接口的做用:

好比有個網站, 須要保存不一樣客戶的信息, 有些客戶從 Web 網站來, 有些客戶從手機客戶端來, 有些客戶直接從後臺管理系統錄入。假設不一樣來源的客戶有不一樣的處理業務流程, 這個時候咱們定義接口來提供一個保存客戶信息的方法,而後不一樣的平臺實現咱們這個保存客戶信息的接口,之後保存客戶信息的話, 咱們只須要知道這個接口就能夠了,具體調用的方法被封裝成了黑盒子,這也就是 Java 的多態的體現,「接口幫助咱們對這些有相同功能的方法作了統一管理」

再好比說,咱們要作一個畫板程序,其中裏面有一個面板類,主要負責繪畫功能,而後你就定義了這個類,但是在不久的未來,你忽然發現這個類知足不了你了,而後你又要從新設計這個類,更糟糕是你可能要廢棄這個現有的類,那麼其餘引用這個類的地方也須要作出修改,顯然這樣很是麻煩。

若是你一開始定義了一個接口,把繪畫功能放在這個接口裏,而後定義類時實現這個接口,那麼你只須要用這個接口去引用實現它的類就好了,之後要修改的話只不過是引用另外一個類而已。「接口的使用提升了代碼的可維護性和可擴展性」

另外,從這兩個例子咱們也能看出,接口不只「下降了代碼的耦合度」,並且僅僅描敘了程序對外的服務,不涉及任何具體的實現細節,這樣也就比較「安全」一些。

參考資料

  • 《Java 核心技術 - 卷 1 基礎知識 - 第 10 版》
  • 《Thinking In Java(Java 編程思想)- 第 4 版》



😁 點擊下方卡片關注公衆號「飛天小牛肉」(專一於分享計算機基礎、Java 基礎和麪試指南的相關原創技術好文,幫助讀者快速掌握高頻重點知識,有的放矢),與小牛肉一塊兒成長、共同進步

   

🎉 並向你們強烈推薦我維護的 Gitee 倉庫 「CS-Wiki」(Gitee 推薦項目,目前已 0.9k star。面向全棧,致力於構建完善的知識體系:數據結構、計算機網絡、操做系統、算法、數據庫、設計模式、Java 技術棧、機器學習、深度學習、強化學習等),相比公衆號,該倉庫擁有更健全的知識體系,歡迎前來 star,倉庫地址 https://gitee.com/veal98/CS-Wiki。也可直接下方掃碼訪問


原創不易,讀完有收穫不妨點贊|分享|在看支持

本文分享自微信公衆號 - 飛天小牛肉(CS-Wiki)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索