Java 接口基礎詳解


目錄

在Java中,接口是一個抽象類型,有點類似於類,但Java接口只能包含方法簽名與屬性,不能包含括方法的實現。



Java接口示例

public interface MyInterface {
    public String hello = "hello";
    public void sayHello();
}

如上所示,java接口是使用關鍵詞interface聲明的。就像類一樣,Java接口可以被聲明為public或者包范圍(無修飾符)。

上面的接口包含了一個變量和一個方法。這個變量可以直接通過這個接口訪問,就像這樣:

System.out.println(MyInterface.hello);

如上所示,訪問接口中的變量與從類中訪問靜態變量非常相似。(接口中的變量被默認聲明為:public static final

但是,在訪問接口中的方法時,必須先從類中實現該方法,下面將解釋如何實現該方法。


實現一個接口

在真正使用接口之前,你必須在Java類中實現該接口。下面的MyInterfaceImpl類實現了上面的MyInterface接口:

public class MyInterfaceImpl implements MyInterface {

    public void sayHello() {
        System.out.println(MyInterface.hello);
    }
}

注意上面MyInterface接口的聲明部分,其中implements標識告訴了Java編譯器MyInterfaceImpl類實現了MyInterface接口。

實現某接口的類必須實現該接口中聲明的所有方法,實現接口方法時必須使用和接口聲明中完全一樣的簽名( 名稱 + 參數 ),接口中的變量不需要類來實現,僅僅需要實現方法。

接口實例

一旦一個Java類實現了一個Java接口,你就可以使用Java類的實例作為該接口的實例。請看下面的示例:

MyInterface myInterface = new MyInterfaceImpl();

myInterface.sayHello();

注意創建MyInterface類型接口實例時,引用了MyInterfaceImpl類創建的對象。Java之所以允許這么做,是因為類MyInterfaceImpl實現了MyInterface接口。你可以將MyInterfaceImpl類的實例作為MyInterface接口的實例。

你不能直接創建Java接口的實例,你必須始終先創建實現某個接口的類的實例,並引用該實例作為接口的實例。


實現多個接口

一個Java類中可以實現多個Java接口。在這種情況情況下,該類必須實現所有所實現接口聲明中的所有方法。這里有個示例:

public class MyInterfaceImpl
    implements MyInterface, MyOtherInterface {

    public void sayHello() {
        System.out.println("Hello");
    }

    public void sayGoodbye() {
        System.out.println("Goodbye");
    }
}

這個類實現了MyInterfaceMyOtherInterface兩個接口,在implements關鍵詞之后列出需要實現的接口的名稱,使用逗號分隔。

如果接口與實現接口的類不在同一個包中,你還需要先導入接口。Java接口是使用import標識導入的,就像類一樣。例如:

import com.jenkov.package1.MyInterface;
import com.jenkov.package2.MyOtherInterface;

public class MyInterfaceImpl implements MyInterface, MyOtherInterface {
    ...
}

下面是由上面的類實現的兩個Java接口:

public interface MyInterface {

    public void sayHello();
}

public interface MyOtherInterface {

    public void sayGoodbye();
}

如你所見,每個接口都包含一個方法,這些方法由類MyInterfaceImpl實現。

方法簽名重疊

如果一個Java類實現了多個接口,那么有些接口可能含有相同的方法簽名(名稱+參數)的風險,由於Java類中一個簽名只能實現一次,所以這可能會導致一些問題。

Java規范中沒有給出解決這個問題的解決方案,在這種情況下該怎么做由你自己決定。


接口變量

Java接口可以包含變量和常量。然而,通常在Java接口中包含變量是沒有意義的。在某些情況下,在Java接口中定義常量是有意義的。特別是這些常量被實現接口的類使用,例如在計算中,或者作為接口中某些方法的參數。然而,我的建議是,如果可以的話,避免在Java接口中放置變量。

所有的變量在接口中都是公共的,即便你在變量中省略了public關鍵詞。


接口方法

一個Java接口中可以包含一個或多個方法的聲明。如前面所述,Java接口不能為這些方法指定任何實現。它由實現接口的類來指定實現。

所有的方法在接口中都是公共的,即便你在方法中省略了public關鍵詞。

接口中可以包含靜態方法。


接口默認方法

在Java 8之前,Java接口不能包含接口的實現,只能包含方法簽名。但是,當API需要向一個接口添加一個方法時,這就會導致一些問題。如果API只是將該方法添加到所需的接口中,則實現該接口的所有類都必須實現該新方法。如果所有實現類都位於該API中,那當然沒有問題。但是,如果某些實現接口的類位於使用該API的客戶端代碼中,則該代碼會被中斷。

讓我們舉例來說明這一點。看看下面這個接口,想象它是一個開源API的一部分,許多應用程序都在內部使用它。

public interface ResourceLoader {

    Resource load(String resourcePath);

}

現在假設一個項目使用了這個API,並通過下面的FileLoader類實現了ResourceLoader接口:

public class FileLoader implements ResourceLoader {

    public Resource load(String resourcePath) {
        // 這里是實現 +
        // 一個返回語句.
    }
}

如果該API的開發人員想在ResourceLoader接口中添加一個新方法,當項目升級到新版本的API時,FileLoader類將被破壞。

為了緩解Java接口中的擴展問題,Java 8中新增了"接口默認方法"這個概念。接口的默認方法可以包含接口的默認實現。實現了該接口的類,但不包含默認接口方法的類,將自動獲得默認方法的實現。

使用default關鍵詞將方法標記為默認方法,下面是一個向ResourceLoader接口添加默認方法的示例:

public interface ResourceLoader {

    Resource load(String resourcePath);

    default Resource load(Path resourcePath) {
        // 提供默認實現
        // 以從指定路徑加載資源
        // 並返回對象中的內容
    }

}

這個示例添加了默認方法load(Path),這個實現省略了實際實現(在方法內部),因為這並不重要,重要的是告訴你如何聲明接口中的默認方法。

類可以通過顯示實現接口默認方法來覆蓋接口默認方法的實現,正如在類中實現接口中的其他方法一樣,類中的任何方法實現都優先於接口默認方法實現。


接口與繼承

Java接口可以從另外一個Java接口中繼承,就像類可以從其他類中繼承一樣。你可以使用extends來指定繼承。下面是一個簡單的接口繼承示例:

public interface MySuperInterface {

    public void saiHello();

}
public interface MySubInterface extends MySuperInterface {

    public void sayGoodbye();
}

MySubInterface接口繼承於MySuperInterface接口。這意味着,MySubInterface將繼承MySuperInterface所有的屬性和方法,實現接口的類必須實現MySubInterfaceMySuperInterface接口中所有的方法。

在子接口中可以定義與父接口中具有相同簽名(名稱+參數)的方法。

與類的繼承不同是,子接口可以繼承多個父接口。列出所有你想要繼承的接口名稱,以逗號分隔。要實現繼承多個父接口的子接口,實現接口的類必須實現子接口與所有父接口中的所有方法。

下面是一個繼承多個父接口的示例:

public interface MySubInterface extends
    SuperInterface1, SuperInterface2 {

    public void sayItAll();
}

在實現多個接口時,多個父接口具有相同的簽名時,沒有規則來說明這種情況。

繼承與默認方法

接口默認方法為接口繼承增加了復雜性。雖然通常一個類可以實現多個接口,即使接口之間具有相同簽名的方法,但是如果這些方法中的一個或多個是默認方法,若沒有在類中覆蓋此方法則會報錯。換言之,如果兩個接口包含相同的方法簽名(名稱+參數),而其中一個接口將此方法聲明為默認方法,則類不能自動實現這個方法(可以在實現接口的類中顯示覆蓋該默認方法)。

如果一個接口繼承自多個接口,並且其中一個或多個接口包含具有相同簽名的方法,並且其中一個父接口將重疊的方法聲明為默認方法,則情況和上面相同。

在上述兩種情況下,編譯器要求實現接口的類需要顯示的實現導致問題的方法。這樣的話,這個類的實現就沒有問題了。類中的實現優先於任何默認實現。


接口與多態性

Java接口是實現多態性的一種手段。多態性是一個需要實踐和思考才能掌握的概念。基本上,多態性意味着類(對象)的實例可以作為不同的類型去使用。在這里,類型指的是一個類或接口。

看看這個簡單的類圖:

接口

在同一個應用程序中使用兩個並行的類層次結構

上面的類模型代表不同類型的車輛和司機,使用屬性和方法來描述它們,這就是類的責任——從現實生活中對這些實體進行建模。

現在假設你需要將這些對象存儲在數據庫中,並將他們序列化為XML、JSON或其他格式。你現在希望在轎車、卡車或車輛對象中使用相同的方法進行操作。這時候就需要實現store()serializeToXML()serializeToJSON()這三個方法操作所有對象。

請先忘記上面這些,假如這些功能直接使用對象中的方法來實現的話,可能會導致混亂的類層次結構。這並不是你希望的方法。

在上面的圖表中,你會把這三種方法放在哪里,以便在所有類上都可以訪問。

解決這個問題的一種常見方法是為車輛和司機這兩個類創建一個超類,它具有存儲和序列化的方法。然而,這將導致概念上的混淆,類層次結構不再為車輛和司機建模,而且還會使車輛和司機與“存儲和序列化機制”相關聯。

更好的解決方案是創建一些存儲和序列化方法有關的接口,並讓類實現這些接口。下面是此類接口的示例:

public interface Storable {
    // 存儲
    public void store();
}
public interface Serializable {
    // 序列化
    public void serializeToXML(Writer writer);
    public void serializeToJSON(Writer writer);
}

當每個類需要實現這兩個接口及其方法時,可以通過將對象強制轉化為該接口類型的實例來訪問這些接口的方法。你不需要確切的知道給定對象的類型是什么,只需要知道它實現的接口,下面是一個示例:

Car car = new Car();

Storable storable = (Storable) car;
storable.store();

Serializable serializable = (Serializable) car;
serializable.serializeToXML (new FileWriter("car.xml"));
serializable.serializeToJSON(new FileWriter("car.json"));

正如你希望的那樣,在類中,接口提供了一個比繼承更干凈的實現跨服務功能的方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM