接口和抽象類之間有什麼區別?

自Java版本8起,抽象類和接口 的定義已經發展起來,瞭解二者之間的區別和交互相當重要。瞭解他們的主要差別將幫助用戶最好地使用這些工具,以充分利用他們的所有潛力。java

抽象類

若是某個類知足如下條件,則將其視爲抽象類:segmentfault

1,由abstract修飾符聲明
2,沒法實例化
3,能夠聲明抽象方法(即,使用abstract修飾符聲明的其餘方法)ide

此外,沒有什麼能夠阻止抽象類實現其全部方法。一個抽象類不須要至少一個抽象方法,可是若是一個類包含一個抽象方法,則必須將其聲明爲abstract。函數

除了這些惟一標識符以外,抽象類還具備其餘任何類的共同特徵。它能夠聲明構造函數,具體方法,變量,常量,靜態成員和嵌套類型。它還能夠實現接口,擴展另外一個類,被其餘類擴展等等。如下是抽象類的示例:工具

public abstract class vehicle {
  private String description;
  public abstract void accelerate () ;
  public abatract void decelerate ();
 //constructors and setters and getters methodz omitted
}

請注意,accelerate和decelerate方法被聲明爲抽象。這就定義了一個通用概念(行駛中的車輛),不能以具體方式(每一個車輛以不一樣的方式行駛)來實現。學習

什麼時候聲明抽象類

聲明至少一個抽象方法的抽象類也必須聲明爲abstract。Vehicle上面示例中的類是抽象的-其accelerate和decelerate方法沒有定義,由於沒有定義車輛身份的定義。想象一下,在不知道是在陸地仍是海上運行的狀況下,解釋了車輛的運動方式。在具體的子類,如Car,Ship,和Plane,擴展抽象定義。每一個子類都提供accelerate和decelerate方法的不一樣實現(或說明)。ui

抽象類是基本的設計工具。聲明抽象類有助於將開發直接引向該抽象類的子類的建立。這將節省時間和必須爲子類實現的代碼。能夠將其插入抽象超類中。這樣,子類– Car,Ship和Plane–中包含的對象就能夠像車輛同樣被使用和分類(請參見下面有關多態的部分)。url

沒有理由擴展抽象類就沒有理由。此外,必須記住抽象類表明的概念對於定義它們的上下文來講太通用了。特別是,abstract修飾符強加了一個重要的設計約束:即便該類定義了其本身的全部方法,也沒法實例化該類。必須經過利用繼承和多態性的好處,用具體的類和子類來擴展它。spa

泛型對象

假設咱們要建立一個應用程序,使您能夠創做和播放音樂。該應用程序必須定義虛擬樂器,即模擬真實樂器的軟件。爲此,咱們能夠建立樂器的層次結構,經過該層次結構,咱們能夠在下面進行報告:設計

public abatract class Instrument {  //抽象類
  private String name ;
  private String type ;
  public abstract void play(String note); 
  // 省略代碼
}
public class Guitar extends Instrument {  
    private int numberofStrings;
    @0verride
    public void play(String note){  // 繼承方法重寫
       //吉他的實現方法
    }
    // 省略其他代碼
}
public abstract class WindInstrument extends Instrument { /* 抽象類
                                                           *擴展
                                                           *I樂器類別 * /
     private String material;
     /* 方法 "play" 繼承了抽象而且沒有覆蓋
      *通用的!* /
    // 省略代碼
}
public class Flute extends WindInstrument { /* 具體類擴展
                                             *管樂器*/
    @Override
    public void play(String note) {
        // 長笛的方法實現.
    }
    //代碼省略
}

在此示例中,咱們看到Instrument既是具體類Guitar(從新定義方法play)又是抽象類WindInstrument(不 覆蓋play抽象方法)的超類。最後,具體類Flute擴展WindInstrument(已經擴展了Instrument)。這個邏輯說明吉他是一種樂器,若是長笛是管樂器,那麼長笛也包含在整個類別的樂器中。咱們沒法從兩個抽象類實例化對象,而且此邏輯與它們的抽象是一致的。

考慮如何實現該類的play方法WindInstrument。現實世界中沒有真正的通用管樂器。該類別的樂器仍然具備特定的名稱,不管是諧波,小號,長笛和單簧管。在此示例中,管樂器過於通用,沒法在咱們的程序中具體定義。這須要不一樣的解決方案。

多態性

抽象類確實能夠避免重複(請注意,某些變量是在抽象類中定義的),而且最重要的是,它能夠利用多態性。例如,咱們的程序能夠定義一個Performer類,該類可使用perform如下樂器演奏任何樂器的音符:

public class Perfomer {
    public void perform(Instrument instrument,String note) {
        instrument.play (note) ;
    }
}

多態性促進了與咱們軟件的交互,由於咱們如今將始終使用相同的方法來演奏任何樂器。

接口定義

像類同樣,接口是用Java語言定義的五種類型之一(其餘三種是從版本14開始的枚舉,註釋和記錄)。從Java版本8開始,接口定義已發生重大變化。實際上,在版本7以前,接口概念很是簡單明瞭,由於全部方法都是隱式的公共和抽象方法。可是,今天的接口是:

1,使用interface關鍵字聲明
2,沒法實例化
3,可以擴展其餘接口
4,一個類能夠實現的多個接口之一
5,可以聲明:
①公共抽象方法– 不須要使用publicandabstract修飾符,它們將由編譯器隱式添加。
②公共默認方法,即用default修飾符標記的具體方法– 不須要使用public修飾符,該修飾符將由編譯器隱式添加。
③具體的私有方法–只能由默認方法調用。
④公共或私有靜態方法–編譯器將隱式認爲沒有訪問說明符的靜態方法是公共的。
⑤靜態和公共常量-它是不須要使用的public,final以及static改性劑和他們將編譯器隱式添加。

在接口內不能聲明其餘任何內容。

其餘高級屬性還能夠表徵接口,例如具備隱式靜態性質的嵌套類型聲明功能。這些屬性對大多數人都不感興趣,由於它們僅在極少數狀況下才有用。

查看如下接口示例:

public interface weighable {
    public static final String UNIT_OF_MEASURE = "kg」;
    public abstract double getWeight () ;
}

咱們能夠等效地經過省略全部修飾符來重寫它:

public interface weighable {
    String UNIT_OF_MEASURE = 「kg」;
    double getWeight ( );
}

請記住,類不能使用extends關鍵字擴展接口,可是它們能夠實現接口。實際上,該implements關鍵字的使用方式extends與產生相似的結果:繼承已實現接口的成員。而後,咱們可使用提供的示例的接口,在一個Article類中實現該接口:

public class Item implementa weighable {
    private double weight ;
    private String description;
    public Item (String description,double weight) {
        setDescription(description) ;
        setWeight (weight) ;
    }
    public double getWeight ( {
        return weight ;
    }
}

接口是抽象類概念的演變

與抽象類相比,咱們能夠強制子類更好地實現抽象方法(在接口中定義)。

在代碼中,接口相似於缺乏內部實現的類。一個接口想用封裝表示咱們所謂的公共接口, 即從外部可見的對象部分,它隱藏了其內部實現。這被視爲對象的未實現部分。

宣言的差別

前面的示例是Java 8以前的接口的出色示例,可是接口名稱已失去其原始含義。儘管老是能夠經過僅聲明抽象方法來使用接口,可是默認和靜態方法的引入意味着就聲明而言,接口如今幾乎等同於抽象類。

若是咱們回顧過去使用interface關鍵字而不是 abstract class,同時認可接口修飾符一般是由編譯器隱式推斷的,則惟一重要的區別是接口沒法聲明實例變量和構造函數。

其他的遵循兩個類似的概念:不可能實例化抽象類和接口,而且抽象類和接口所提供的共同優勢是它們能夠強制子類實現行爲。繼承抽象方法的類必須重寫繼承的方法,或者將其聲明爲抽象自己。在設計中,這些工具相似地支持抽象。

概念差別

概念上最重要但常常被忽略的差別之一。抽象類應定義一個過於籠統的抽象,沒法在聲明它的上下文中實例化。在Vehicle該類中找到了一個很好的例子:

public abstract class vehicle {
    private String description ;
    public abstract void accelerate() ;
    public abstract void decelerate() ;
}

所以,咱們能夠將Vehicle類擴展到Plane上一個示例中的類,該示例將具備accelerate和decelerate方法的本身的重寫實現:

public class Plane extends vehicle {
    @Override
    public void accelerate( {
        // 重寫繼承的方法
        // 省略實現
    }
    @Override
    public void decelerate( {
         // 重寫繼承的方法
         // 省略實現
    }
}

接口應抽象出多個類能夠實現的行爲,而且不該實例化行爲。不該該有任何對象表明的行爲和接口常用的形容詞和行爲的名稱(Weighable,Comparable,Cloneable等)。對象應實現一種或多種行爲。

例如,咱們能夠引入一個Flying接口,該接口將由表示飛行物體的類實現。注意名稱如何暗示行爲而不是抽象對象:

public interface Flying {
    void land() ;
    void takeoff ();
}

每一個必須抽象飛行物體的概念的類(例如飛機,無人駕駛飛機或什至是鳥)都必須實現該Flying接口。這意味着咱們能夠Plane按如下方式重寫該類:

public clasz Plane extends vehicle implements Flying {
    @Override
    public void land() {
         //overrides the method of the Flying interface
    }
    @Override
    public void takeOff () {
         // overrides the method of the Flying interface
    }
    @Override
    public void accelerate() {
         // overrides the method of the vehicle abstract clasz
    }
    @Override
    public void decelerate() {
         //overrides the method of the vehicle abatract class
    }
}

image.png
而後,咱們能夠建立多態參數以利用該Flying接口:

public class ControlTower {
    public void authorizeLanding (Flying v){
        v.land() ;
    }
    public void authorizeTakeOff(Flying v){
        v.takeOff ();
    }
}

這使咱們能夠將已經由實現Flying接口的類已經建立的飛行對象傳遞給這些方法。順序相反:Plane由抽象類定義的對象Vehicle具備經過Flying接口說明的加速和減速參數。

多重繼承

類和接口之間最著名和最重要的區別在於繼承。一次只能擴展一個類,可是能夠實現任何數量的接口。Java 8中默認方法的引入使得必須指出,該功能的簡化版本(多重繼承)已最終引入該語言中。

咱們討論簡化是由於類只能從接口繼承其功能部分(方法)。

除了接口能夠聲明的任何靜態常量以外,它們都不能繼承數據。可是,在其餘語言中,定義了徹底的多重繼承,這帶來了複雜的規則來管理由其實現引發的問題。

甚至能夠在Java 8以前實現多個接口,可是繼承的抽象方法老是必須被重寫。隨着默認方法的出現,多重繼承的含義與過去有所不一樣。若是咱們考慮如下接口:

public interface Reader {
    default void read (Book book) {
        System. out. println("I'm reading: 「 + book.getTitle() + 」 by 」
              +book.getAuthor() :
    }
}
public interface Programmer {
    def ault void program(String language){
        System.out. println("I'm programming with」 + language) ;
    } 
}

咱們能夠建立如下實現兩個接口並繼承其方法的類:

public class WhosReading implements Reader,Programmer {

}

這是一個使用示例:

public clasz MultipleInheritanceTest {
    public static void main(String args[]) {
        var you = new WhozReading () :
        var javaForAliens = new Book ("Java for Aliens" , "Claudio De Sio Cesari" );
        you. read(javaFor&liens) ;
        you. program ("Java」);
    }
}

經過同時擴展一個類(是否抽象)和一個或多個接口來實現Java中的多個繼承是不可能的。

繼承適用性

另外一個不爲人知的差別涉及繼承的適用性。只有一個類能夠擴展另外一個類,而其餘任何類型的Java都不能作到這一點。相反,接口能夠擴展其餘接口,但不能擴展類(抽象或具體)。此外,接口能夠經過類,枚舉和記錄來實現,所以也能夠利用繼承的默認方法。

在Java 14中做爲功能預覽引入的記錄容許以最小的語法定義表示不可變數據的類。它們是不可擴展的,不能擴展類。這是由於在編譯時,記錄被轉換爲最終類,從而擴展了該類,所以沒法擴展其餘類。幸運的是,他們能夠實現接口。例如,使用如下Designer界面:

public interface Designer {
    default void design(String tool) {
        System.out.println("I am designing software with「 + tool) :
    }
}

咱們能夠Employee經過如下方式建立記錄:

public record Employee (String name,int id) implements Designer { }

並將其與如下代碼一塊兒使用:

Employee serj = new Employee("serj", 10) ;
serj.design("UML」)∶

而後得到輸出:

l am designing software with UML

枚舉能夠實現接口,但不能擴展類,由於在編譯時它們被轉換爲擴展java.lang.Enum該類的類。

結論

隨着源自Java 8的接口的發展,抽象類之間的技術差別有所減小。抽象類和接口都不能實例化,抽象類和接口均可以強制子類實現抽象方法。確保還記得其餘更集中的區別:

1,除靜態和公共常量外,接口沒法聲明數據。
2,抽象類應該抽象化過於通用而沒法實例化的對象,而接口應該抽象出不一樣對象能夠實現的行爲。
3,一次只能擴展一個類,可是能夠實現多個接口。
4,類只能由其餘類擴展,而接口也能夠由枚舉和記錄實現。

更多Java基礎學習能夠加入個人十年Java學習園地

相關文章
相關標籤/搜索