Java面向對象[接口]

抽象類是從多個類中抽象出來的模板,如果將這種抽象進行的更徹底,則可以提煉出一種更加特殊的“抽象類”————接口
從Java9開始,允許在接口中定義默認方法和類方法,默認方法和類方法都可以提供方法實現,還增加了一種私有方法,私有方法也可以提供方法實現。

接口的概念和作用

  • 接口定義了某一批類所需要遵守的規範,接口不關心這些類的內部狀態數據,也不關心這些類裏方法的實現細節,它只規定這些類裏必須提供某些方法,提供這些方法的類就可以滿足實際需要
  • 接口是從多個相似類中抽象出來的規範,接口不提供實現,它體現的是規範和實現分離
  • 系統各模塊之間也應該採用這種面向接口的耦合,從而儘量降低各模塊之間的耦合,爲系統提供更好的可擴展性和可維護性
  • 接口定義的是多個類共同的行爲規範,這些行爲是與外部交流的通道,這就意味着接口裏通常是定義一組方法

接口定義

[修飾符]interface 接口名 extends 父接口1, 父接口2...
{
    零個到多個常量定義...
    零個到多個抽象方法定義...
    零個到多個內部類、接口、枚舉定義...
    零個到多個私有方法、默認方法或者類方法定義...
}
  • 修飾符可以是public也可以省略,如果省略則默認採用包權限訪問控制符,即只有在相同包結構下纔可以訪問該接口
  • 接口名應與類名採用相同的命名規則,從語法角度來說,接口名只要是合法的標識符即可,但如果遵守Java的可讀性規範,則接口名應該由多個有意義的單詞連綴而成,每個單詞首字母大寫,單詞與單詞之間無需任何分隔符,而且可以使用形容詞
  • 一個接口可以有多個直接父接口,但接口只能繼承接口不能繼承類
  • Java8及以上才允許在接口中定義默認方法、類方法
  • 接口定義的是一種規範,它不能包含構造器和初始化塊,可以包含成員變量(只能是靜態常量)、方法(只能是抽象方法、類方法、默認方法或私有方法)、內部類(包括內部接口、枚舉)
  • 接口裏定義的是多個類共同的公共行爲規範,因此接口裏的常量、方法、內部類和內部枚舉都是public訪問權限,定義接口成員時可以省略訪問控制修飾符,如果指定訪問控制修飾符則只能用public
  • Java9新增了一種方法叫私有方法,主要作用是作爲工具方法,爲接口中的默認方法和類方法提供支持,私有方法可以擁有方法體,但不能使用default修飾;私有方法可以使用static修飾,也就是說私有方法可以是類方法也可以是實例方法
  • 對於接口的靜態常量而言,系統會自動爲這些成員變量增加static和final修飾符,也就是說接口定義成員變量時,不管是否使用public static final修飾,接口裏的成員變量總是使用這三個修飾符來修飾,而且接口沒有構造器和初始化塊,因此接口裏定義的成員變量只能在定義時指定默認值
  • 接口裏定義的方法只能是抽象方法、類方法、默認方法或私有方法,如果不是定義默認方法、類方法或私有方法,系統將自動爲普通方法增加abstract修飾符,也就是說接口裏的普通方法不管是否顯示的使用了public abstract修飾符,系統都會給它加上當成抽象方法處理,因此普通方法也不能有方法體;但類方法、默認方法和私有方法必須有方法體
  • 接口裏定義的內部類、內部接口、內部枚舉默認都採用public static兩個修飾符,不管定義時是否指定了兩個修飾符,系統都會自動使用public static對他們進行修飾
package davieyang;
public interface Output
{
    // 接口裏定義的成員變量只能是常量
    int MAX_CACHE_LINE = 50;
    // 接口裏定義的普通方法只能是public的抽象方法
    void out();
    void getData(String msg);
    // 在接口中定義默認方法,需要使用default修飾
    default void print(String... msgs)
    {
        for (var msg : msgs)
        {
            System.out.println(msg);
        }
    }
    // 在接口中定義默認方法,需要使用default修飾
    default void test()
    {
        System.out.println("默認的test()方法");
    }
    // 在接口中定義類方法,需要使用static修飾
    static String staticTest()
    {
        return "接口裏的類方法";
    }
    // 定義私有方法
    private void foo()
    {
        System.out.println("foo私有方法");
    }
    // 定義私有靜態方法
    private static void bar()
    {
        System.out.println("bar私有靜態方法");
    }
}
  • Java8允許接口定義默認方法,默認方法必須使用default修飾並且不能使用static
  • 無論程序是否指定,默認方法總是使用public修飾,即便開發者沒有顯示的指定public,系統會自動爲默認方法添加public
  • 由於默認方法不能用static,因此不能直接使用接口來調用默認方法,需要使用接口的實現類的實例來調用這些默認方法
  • 接口的默認方法其實實例方法,在早起的java裏接口中的實例方法不能有方法體,在不破壞以前的規則情況下,增加了默認方法,默認方法就是有方法體的實例方法
  • Java8還允許在接口中定義類方法,類方法必須使用static,且不能使用default修飾,無論程序是否指定,類方法總是使用public修飾,即便開發者沒有指定public,系統會自動爲類方法添加public修飾符,類方法可以直接使用接口來調用
  • Java8之後接口可以定義這麼多種方法,難免會出現重複的實現邏輯,因此Java9新增了私有方法,用於將這些邏輯提煉出來成爲工具方法,而工具方法應該被隱藏。
  • 接口裏的成員變量默認使用public static final修飾,因此即使類處於不同的package下,也可以通過接口來訪問接口裏的成員變量
package leadscloud;
public class OutputFieldTest
{
    public static void main(String[] args)
    {
        // 訪問另一個包中的Output接口的MAX_CACHE_LINE
        System.out.println(davieyang.Output.MAX_CACHE_LINE);
        // 下面語句將引起"爲final變量賦值"的編譯異常
        // davieyang.Output.MAX_CACHE_LINE = 20;
        // 使用接口來調用類方法
        System.out.println(davieyang.Output.staticTest());
    }
}

從某個角度看,接口可以被當成特殊的類,因此一個Java源文件裏最多只能有一個public接口,如果一個Java源文件裏定義了一個public接口,則該源文件的主文件名必須與該接口名相同

接口的繼承

  • 接口繼承和類的繼承不一樣,接口支持多繼承,即一個接口可以有多個直接父接口
  • 和類繼承相似的是子接口擴展某個父接口,將會獲得父接口裏定義的所有抽象方法、常量
interface InterfaceA
{
    int PROP_A = 5;
    void testA();
}
interface InterfaceB
{
    int PROP_B = 6;
    void testB();
}
interface InterfaceC extends InterfaceA, InterfaceB
{
    int PROP_C = 7;
    void testC();
}
public class InterfaceExtendsTest
{
    public static void main(String[] args)
    {
        // 通過InterfaceC來訪問繼承來的常量
        System.out.println(InterfaceC.PROP_A);
        System.out.println(InterfaceC.PROP_B);
        System.out.println(InterfaceC.PROP_C);
    }
}

使用接口

接口不能創建實例,但接口可以用於聲明引用類型變量,當使用接口來聲明引用類型變量時,這個引用類型變量必須引用到其實現類的對象,除此之外,接口的主要用途就是被實現類實現,歸納起來主要用途如下:

  • 定義變量,也可用於進行強制類型轉換
  • 調用接口中定義的常量
  • 被其他類實現

實現接口

一個類可以實現一個或多個接口,繼承使用extends關鍵字,實現則使用implements關鍵字。

[修飾符]class 類名 extends 父類 implements 接口1,接口2....
{
    類體部分
}
  • 實現接口與繼承父類相似,一樣可以獲得所實現接口裏定義的常量(成員變量)、方法(包括抽象方法和默認方法)
  • 一個類可以繼承一個父類,並同時實現多個接口,implements部分必須方法extends部分之後
  • 一個類實現了一個或者多個接口之後,這個類必須完全實現這些接口裏定義的全部抽象方法(也就是重寫這些抽象方法),否則該類將保留從父接口那裏繼承到的抽象方法,該類也必須定義成抽象類。
  • 一個類實現某個接口時,該類將會獲得接口中定義的常量(成員變量)、方法等,因此可以把實現接口理解爲一種特殊的繼承,相當於實現類繼承了一個徹底抽象的類(相當於除默認方法外,所有方法都是抽象方法的類)
import davieyang.Output;
// 定義一個接口
interface Product
{
    int getProduceTime();
}
// 讓Printer類實現Output和Product接口
public class Printer implements Output, Product
{
    private String[] printData = new String[MAX_CACHE_LINE];
    // 用以記錄當前需打印的作業數
    private int dataNum = 0;
    public void out()
    {
        // 只要還有作業,繼續打印
        while (dataNum > 0)
        {
            System.out.println("打印機打印:" + printData[0]);
            // 把作業隊列整體前移一位,並將剩下的作業數減1
            System.arraycopy(printData, 1, printData, 0, --dataNum);
        }
    }
    public void getData(String msg)
    {
        if (dataNum >= MAX_CACHE_LINE)
        {
            System.out.println("輸出隊列已滿,添加失敗");
        }
        else
        {
            // 把打印數據添加到隊列裏,已保存數據的數量加1。
            printData[dataNum++] = msg;
        }
    }
    public int getProduceTime()
    {
        return 45;
    }
    public static void main(String[] args)
    {
        // 創建一個Printer對象,當成Output使用
        Output o = new Printer();
        o.getData("輕量級Java EE企業應用實戰");
        o.getData("瘋狂Java講義");
        o.out();
        o.getData("瘋狂Android講義");
        o.getData("瘋狂Ajax講義");
        o.out();
        // 調用Output接口中定義的默認方法
        o.print("孫悟空", "豬八戒", "白骨精");
        o.test();
        // 創建一個Printer對象,當成Product使用
        Product p = new Printer();
        System.out.println(p.getProduceTime());
        // 所有接口類型的引用變量都可直接賦給Object類型的變量
        Object obj = p;
    }
}
  • Printer類實現了Output接口和Product接口,因此Printer對象既可以直接賦給Output變量,也可以直接賦給Product變量,彷彿Printer類既是Output類的子類,也是Product類的子類,這就是Java提供的模擬多繼承
  • 程序中Printer實現了Output接口,即可獲得Output接口中定義的print()和test()兩個默認方法,因此Printer實例可以直接調用這兩個默認方法
  • 實現接口方法時,必須使用public訪問控制修飾符,因爲接口裏的方法都是public的,而子類(相當於實現類)重寫父類方法時訪問權限只能更大或者相當,所以實現類實現接口裏的方法時只能使用public訪問權限
  • 接口不能顯示的繼承任何類,但所有接口類型的應用變量都可以直接賦給Object類型的引用變量,因此在上面的程序中可以把Product類型的變量直接賦給Object類型變量,這也是利用向上轉型來實現的,編譯器知道任何Java對象都必須是Object或其子類的實例,Product類型的對象也不例外(它必須是Product接口實現類的對象,該實現類肯定是Object的顯示或隱式子類)

接口和抽象類的聯繫與區別

接口和抽象類共同特徵

  • 接口和抽象類都不能被實例化,他們都位於繼承樹的頂端,用於被其他類實現和繼承
  • 接口和抽象類都可以包含抽象方法,實現接口或繼承抽象類的普通子類都必須實現這些抽象方法

接口和抽象類區別

意義上的區別

  • 接口體現的是一種規範,對於接口的實現者而言,接口規定了實現者必須向外提供哪些服務(以方法的形式來提供);對於接口的調用者而言,接口規定了調用者可以調用哪些服務,以及如何調用這些服務(就是如何來調用方法);
  • 當在一個程序中使用接口時,接口是多個模塊間的偶和標準;當在多個應用程序之間使用接口時,接口是多個程序之間的通信標準
  • 接口類似於整個系統的“總綱”,制訂了系統各模塊應該遵循的標準,因此一個系統中的接口不應該經常改變,對整個系統甚至其他系統將是輻射式的,導致系統中大部分類都需要改寫
  • 抽象類則不一樣,它作爲系統中多個子類的共同父類,它所體現的是一種模板式設計,抽象類作爲多個子類的抽象父類,可以被當成系統實現過程中的中間產品,這個中間產品已經實現了系統的部分功能(那些已經提供實現的方法),但這個中間產品需要進一步完善

用法上的區別

  • 接口裏只能包含抽象方法、靜態方法、默認方法和私有方法,不能爲普通方法提供方法實現;抽象類則安全可以包含普通方法
  • 接口裏只能定義靜態常量,不能定義普通成員變量;抽象類裏則既可以定義普通成員變量,也可以定義靜態常量
  • 接口裏不包含構造器;抽象類裏可以包含構造器,但抽象類的構造器不是用來實例化,而是用來讓其子類調用這些構造器來完成屬於抽象類的初始化操作
  • 接口不能包含初始化塊;但抽象類則完全可以包含初始化塊
  • 一個類最多只能有一個直接父類,包括抽象類;但一個類可以直接實現多個接口,通過實現多個接口可以彌補Java的單繼承不足
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章