天天看點

【Java核心技術卷】深入了解Java的接口

接口是Java中非常核心的一部分,我之前寫了一篇博文,名字是 了解Java的記憶體邏輯對象模型

接口結束後除了Mark Word和反射類指針沒有說外,其他的都已經說過了.

接口部分對應的是接口偏移量表指針 和 接口方法表指針.

.

學習接口之前,要對方法 和 繼承有比較好的了解,下面進行回顧一下 :

在對象模型中,對象之間的關系:

1, 執行個體與執行個體的組合關系:有多種語義關系

2, 執行個體與類的關系:

  • 執行個體對象—類:
  • 類—反射類:
  • 反射類—反射類:

3、繼承關系:

  • 類—類繼承: 實作繼承(繼承的方法有實作)和資料結構的繼承
  • 類—接口繼承: 聲明繼承(繼承的方法隻有聲明,沒有實作)
  • 接口—接口繼承:契約繼承(告訴實作類,實作一個接口,還需要實作該接口繼承的接口)

    Java語言對象模型中是通過不同的執行個體對象結構、類型對象結構資料結構來區分執行個體對象和類型對象。引用類型主要三種:類類型、接口類型、數組類型。如果對接口類型之外的兩種類型不太了解,可以參考我的專欄

    Java核心技術 卷1

對象之間的關系了解之後,讓我們看一下 方法:

1.方法定義

1) 方法聲明

ReturnType method_name1(parameter_list)

2) 方法體(方法實作)

有一對花括号括起來的部分就是方法體

{

int x = 26;// 方法體中定義的變量可以初始化

int y = 28;

x = x + y;

}

由JVM完成方法聲明到方法實作的映射。

2. 方法的調用

将一個方法調用同一個方法聲明連接配接到一起就稱為“綁定“(Binding)。

由編譯器和解釋器共同完成。 可以參考多态那篇博文,這裡僅僅是簡單的提一下.

1) 靜态綁定調用:由變量的類型決定調用的代碼;在編譯時,就确定了具體的調用的方法代碼,稱為靜态綁定

編譯時靜态綁定:變量類型--->類--->靜态綁定類或超類中的方法聲明

解釋時靜态綁定調用:直接執行該方法的實作代碼

a、 靜态方法

b、 實方法(private、final virtual)

c、 super調用的方法

d、 new調用的構造方法

2) 動态綁定調用:由變量的類型、變量引用的執行個體對象決定調用的代碼

動态多态:即需要在運作,再确定,也稱為動态綁定。

a. 類虛方法調用:

變量類型--->執行個體對象--->類--->動态綁定類中的方法--->執行代碼

b. 接口方法的調用:

變量類型--->執行個體對象--->類--->動态綁定接口中的方法--->類中接口方法映射的方法--->執行代碼

3) 反射調用

【Java核心技術卷】深入了解Java的接口

這張圖說明了接口方法動态綁定的多個過程, 如果你還不了解Java類之間的多态,也就是虛方法的動态綁定在各個過程的情況, 我這裡解釋也沒什麼用, 如果了解的話,通過對照Java的記憶體對象模型進行了解.

3.方法的分類

1) 靜态方法

2) 執行個體方法

  • 實方法
  • 虛方法
  • 抽象方法(純虛方法)

3) 構造方法

再簡單回顧一下繼承:

繼承有下面兩種情況(含拓展)

類—類繼承的公共成員:

1.公共執行個體資料結構(執行個體字段)

2.公共實方法

3.公共虛方法

4.公共抽象方法

類—接口繼承的通用成員:

1.通用抽象方法

2.預設通用虛方法

類—類繼承和類—接口繼承隻有一個相同之處,那就是都繼承了抽象方法。

程式在OS系統中的運作結果:(對象模型、執行模型)

  1. 生成資料結構
  2. 通路資料結構
  3. 加工資料結構中的資料

    執行模型離不開對象模型,之前也介紹過了,這裡也不再贅述.

關于類、抽象類、接口

普通類(一定有實作)------抽象類(可能有實作)------接口類(一定沒實作),

抽象類是普通類與接口之間的一種中庸之道,是一個半成品。

抽象類中可能存在的繼承的公共成員:

  1. 公共執行個體資料結構
  2. 公共執行個體方法
  3. 公共虛方法
  4. 公共抽象方法

接口中可能存在的繼承的通用成員:

  1. 通用抽象方法
  2. 預設通用虛方法

普通類中可能存在的繼承的公共成員:

  1. 執行個體資料結構
  2. 執行個體方法

關于Java棧、Java堆、Java方法區

  1. Java棧

    Java棧由JVM建立,程式運作在棧中産生局部變量和形參

  2. Java堆

    Java堆由JVM管理,程式運作在堆中産生執行個體對象

  3. Java方法區

    Java方法區完全由JVM建立控制,存放類對象、方法代碼、常量池(Java8)。

鋪墊了那麼多,下面進行對接口的了解:

Java語言與C++語言兩大差別:沒有多繼承、沒有指針。

通過接口,能夠實作多繼承

如果Java的類也能夠多繼承的話,會導緻程式的混亂, 耦合度增大,而且也帶來了很多的問題和注意事項.

接口定義:

接口是引用類型的一種,與類相似,但也存在諸多不同(類類型、接口類型、數組類型均是引用類型)。

接口名一般為名詞,也可以是以able結尾的形容詞(例如接口名skinnable 可換膚的)

接口是功能的抽象,而抽象類是概念的抽象(一般具有相同特征值),抽象類是半成品類。

例如:空調車,車是抽象概念,而空調是車的功能。即車是抽象類,而空調是接口。

類之間的繼承很好了解, 類繼承接口為聲明繼承也很好了解,關鍵的是接口與接口之間的繼承,有點特殊,它不像類與類之間的繼承是父與子之間的那種意義, 它所展現的是功能上的繼承. 也就是前面所說的契約繼承.

接口是一種規範、是合約,隻有方法的聲明和方法的功能語義描述,而沒有實作;接口中的一組抽象方法構成了實作該接口的在樹形層次結構上可能毫不相幹的不同實作類共同遵守的約定,描述了實作類應該具有的一組通用功能,而功能方法和執行個體域應該由實作類來實作。

接口和類的差别在于:類可以維護狀态資訊,而接口不能。接口不能有執行個體變量。

常使用接口來建立類和類之間的“協定”。

使用接口的核心原因:

一、 幫助實作類實作多繼承,帶來靈活性。能夠向上轉型為多個基類型。

二、 防止用戶端程式員建立該類的執行個體對象

如果要建立隻有抽象方法的純抽象基類,那麼就應該選擇接口而不是純抽象類。

接口将方法的聲明和方法的實作分離,使得聲明和實作完全解耦,因而接口可以應用于(映射到)不同的具體實作,是以代碼也就更具可複用性。(這個可以與C++中類的多繼承進行比較一下)

接口定義:

1、接口聲明

Interface interface_name extends interface1_name, interface2_name //接口可以多繼承,是契約繼承           

2、接口體:有一對花括号括起來的部分就是接口體。

子接口接口體中可以對父接口的方法和常量進行隐藏 new。

多重繼承

一個類隻能有一個父類,但是可以繼承多個接口。可以提供多重繼承的大多好處,同時還能避免C++類多重繼承的複雜性和低效性。

【Java核心技術卷】深入了解Java的接口

接口的繼承

專用接口 繼承 通用接口。

是契約/合約繼承,告訴實作類,實作一個接口,還需要實作該接口繼承的接口。

【Java核心技術卷】深入了解Java的接口

接口的擴充允許存在多條從具有較高通用性的接口到較高專用性的接口的鍊。

面向接口程式設計

方法的形參或傳回值的類型可以為接口,字段可以為接口,即為面向接口的程式設計。

接口變量和類變量引用的執行個體對象的範圍不一樣,面向接口的程式設計比面向類的程式設計,耦合少、可維護、可擴充。

【Java核心技術卷】深入了解Java的接口

在設計類時隻要可能/可行,就應該使用接口而不是類作為類型進行以下聲明:

1、 方法形參的類型

2、 局部變量的類型是接口類型

3、(私有)執行個體字段的類型是接口類型

4、 靜态字段的類型是接口類型

5、 方法傳回值的類型是接口類型

面向接口程式設計的優點:

1) 面向接口程式設計,接口可以被多個不同繼承結構中的類來實作。接口類型的形參可以引用不同繼承結構中的實作類的執行個體對象。(面向類程式設計,基類可以被一個相同繼承結構中的類來實作。基類型的形參隻可以引用相同繼承結構中的派生類的執行個體對象。并且需要強類型轉換)

2) 面向接口程式設計,編譯時接口方法調用代碼可以和類完全解耦和(代碼中不出現類)。接口變量調用的是接口的方法,而類變量調用的是類的方法。

接口的多态

【Java核心技術卷】深入了解Java的接口

接口和抽象類

抽象類:

1、編譯器:文法上,告知編譯器該類不能new建立執行個體,隻能被當作父類被繼承。new該類将文法出錯。

2、程式設計原因:

1)在經過抽象後,生成的類在現實中,不存在該類的執行個體,即該類的執行個體在現實中無意義。

2)在應用程式中,不需要生成該類的執行個體。

3、抽象類作為子類的模版,避免了子類設計的随意性。

抽象方法:

1、編譯器:文法上,告知編譯器該方法隻有聲明,而沒有實作,類為抽象類。調用該方法将文法出錯。

2、程式設計原因:

1)在該抽象類中,無法實作該方法。即該方法的實作無意義。

2)在應用程式中,不需要調用該類的方法,代碼的實作在派生類中。

文法上的抽象類在語義上可以分為:

1、普通抽象類: 含有方法實作或含有執行個體字段

2、純抽象類:隻含有抽象方法

【Java核心技術卷】深入了解Java的接口

應該使用接口的四種情況:

【Java核心技術卷】深入了解Java的接口

接口程式

1. 接口聲明及接口成員

//1 定義接口
public interface MyInterface {
    // 接口裡定義的成員變量隻能是靜态常量
    //必須進行初始化
    //命名規範,所有字母大寫
    //預設:public static final
    int MAX_SIZE = 50;

    // 接口裡定義的普通方法隻能是public的抽象方法
    //預設:public abstract
    void delMsg();

    // 接口裡定義的普通方法隻能是public的抽象方法
    //預設:public abstract
    void addMsg(String msg);

    //Java8及其以上版本,允許在接口中定義預設方法
    //不能直接使用接口來調用預設方法,一個接口中可以有多個預設方法
    // 在接口中定義預設方法,需要使用default修飾,帶實作的public的接口虛方法
    //預設:public
    default void print(String... msgs) {
        for (String msg : msgs) {
            System.out.println(msg);
        }
    }

    //Java8及其以上版本,允許在接口中定義類方法
    // 在接口中定義類方法,需要使用static修飾
    //預設:public
    static String staticTest() {
        return "接口裡的類方法";
    }
}           

2. 單個接口的實作---default

//2 實作接口(類--接口的繼承)

//2-1 實作單接口---default

//實作接口
public class Main implements MyInterface {

    // 定義個一個字元串數組,長度是接口中定義的常量MAX_SIZE
    private String[] msgs = new String[MyInterface.MAX_SIZE];
    // 記錄消息個數
    private int num = 0;

    // 實作接口中的方法
    public void delMsg() {
        if (num <= 0) {
            System.out.println("消息隊列已空,删除失敗!");
        } else {
            // 删除消息,num數量減1
            msgs[--num] = null;
        }
    }

    // 實作接口中的方法
    public void addMsg(String msg) {
        if (num >= MyInterface.MAX_SIZE) {
            System.out.println("消息隊列已滿,添加失敗!");
        } else {
            // 将消息添加到字元串數組中,num數量加1
            msgs[num++] = msg;
        }
    }

    // 定義一個實作類自己的方法
    public void showMsg() {
        // 輸出消息隊列中的資訊
        for (int i = 0; i < num; i++) {
            System.out.println(msgs[i]);
        }
    }

    public static void main(String[] args) {
        // 執行個體化一個接口實作類的對象,并将其指派給一個接口變量引用
        MyInterface mi = new Main();
        // 調用接口的預設方法,預設方法必須通過執行個體對象來調用
        mi.print("張三", "李四", "王五");
        // 調用接口的類方法,直接通過“接口名.類方法()”來調用
        System.out.println(MyInterface.staticTest());

        System.out.println("------------------------");

        // 執行個體化接口實作類
        Main ifd = new Main();
        // 添加資訊
        ifd.addMsg("Java 8應用開發");
        ifd.addMsg("歡迎來實訓");
        ifd.addMsg("My name's zhaokel");
        ifd.addMsg("這是一個測試");
        // 輸出資訊
        ifd.showMsg();

        System.out.println("------------------------");

        // 删除一個資訊
        ifd.delMsg();
        System.out.println("删除一個資料後,剩下的資訊是:");
        ifd.showMsg();
    }

}
           

結果:

【Java核心技術卷】深入了解Java的接口

這裡對接口方法表指針做一個介紹,它 相當于數組,雖然這裡僅僅隻有一個接口,但我還是示範一下吧

JVM設定接口方法表的時候,要通過接口偏移量表找到所有的接口(相當于把該類或者接口繼承的 接口 的邏輯綁在一起)

注意的是接口虛表和類的虛表不在同一資料結構内, 當JVM把接口表設定完成後,要進行接口方法聲明和類方法聲明的映射(要求有代碼實作),如果确認沒問題了, 将接口虛表索引與接口方法表指針聯系在一起,通過接口方法表指針就能夠找到所有繼承的接口方法了..

以 ifd.addMsg("Java 8應用開發");

這行代碼為例,首先通過ifd引用找到Main類的實作類, 通過接口方法表指針查找接口方法addMsg(),找到之後,通過接口方法和該類實作方法的映射,找到對應的代碼,進行執行.

其他的都與此類似.後面相同的問題,也不多說.

3. 多個接口的實作

//2 實作接口(類--接口的繼承)
//2-2 實作多接口
// Multiple interfaces.

interface CanFight {
    void fight();
}

interface CanSwim {
    void swim();
}

interface CanFly {
    void fly();
}

class ActionCharacter {
    public void fight() {
        System.out.println("CanFight...");
    }
}

class Hero extends ActionCharacter
        implements CanFight, CanSwim, CanFly {
    public void swim() {
        System.out.println("CanSwim...");
    }

    public void fly() {
        System.out.println("CanFly...");
    }
}

public class Main {
    public static void t(CanFight x) { x.fight(); }
    public static void u(CanSwim x) { x.swim(); }
    public static void v(CanFly x) { x.fly(); }
    public static void w(ActionCharacter x) { x.fight(); }
    public static void main(String[] args) {
        Hero h = new Hero();
        t(h); // Treat it as a CanFight
        u(h); // Treat it as a CanSwim
        v(h); // Treat it as a CanFly
        w(h); // Treat it as an ActionCharacter
    }
}
           
【Java核心技術卷】深入了解Java的接口

這個例子涉及到多态

Hero extends ActionCharacter implements CanFight, CanSwim, CanFly

因為 ActionCharacter 實作了CanFight方法且能夠與繼承的接口對象方法形成映射, 無需再次定義.

public static void u(CanSwim x) { x.swim(); } 以這個為例子

傳入的參數為Hero

首先确認swim()方法在CanSwim接口中的槽号, 編譯器記錄槽号, 然後通過接口偏移量表指針找到該接口,記錄偏移量,接着通過接口方法表指針通路接口方法表,找到對應的槽号,然後通過映射找到相應的代碼,進行執行.

4. 類的派生和接口的重新實作

//2 實作接口(類--接口的繼承)
//2-3 重新實作接口--多态
// Multiple interfaces.

interface CanFight {
    void fight();
}

interface CanSwim {
    void swim();
}

interface CanFly {
    void fly();
}

class ActionCharacter
        implements CanFight {

    public void fight() {
        System.out.println("CanFight1...");
    }
}

class Hero extends ActionCharacter
        implements CanSwim, CanFly {

    public void fight() {
        System.out.println("CanFight2...");
    }

    public void swim() {
        System.out.println("CanSwim...");
    }

    public void fly() {
        System.out.println("CanFly...");
    }

}

public class Main {
    public static void t(CanFight x) { x.fight(); }
    public static void u(CanSwim x) { x.swim(); }
    public static void v(CanFly x) { x.fly(); }
    public static void w(ActionCharacter x) { x.fight(); }
    public static void main(String[] args) {
        ActionCharacter a = new ActionCharacter();
        Hero h = new Hero();

        //虛方法多态
        w(a); // Treat it as an ActionCharacter
        w(h); // Treat it as an ActionCharacter

        //接口多态
        t(a); // Treat it as a CanFight
        t(h); // Treat it as a CanFight

        u(h); // Treat it as a CanSwim
        v(h); // Treat it as a CanFly
    }
}           
【Java核心技術卷】深入了解Java的接口

5. 接口的繼承1

//3 接口的繼承(接口--接口的繼承)
//3-1

//接口的繼承
//第一個接口
interface InterfaceA {
    int V_A = 10;

    void testA();
}

// 第二個接口
interface InterfaceB {
    int V_B = 20;

    void testB();
}

// 第三個接口
interface InterfaceC extends InterfaceA, InterfaceB {
    int V_C = 30;

    void testC();
}

// 實作第三個接口
public class Main implements InterfaceC {
    // 實作三個抽象方法
    public void testA() {
        System.out.println("testA()方法");

    }

    public void testB() {
        System.out.println("testB()方法");

    }

    public void testC() {
        System.out.println("testC()方法");

    }

    public static void main(String[] args) {
        // 使用第三個接口可以直接通路V_A、V_B和V_C常量
        System.out.println(InterfaceC.V_A);
        System.out.println(InterfaceC.V_B);
        System.out.println(InterfaceC.V_C);
        // 聲明第三個接口變量,并指向其實作類的執行個體對象
        InterfaceC ic = new Main();
        // 調用接口中的方法
        ic.testA();
        ic.testB();
        ic.testC();
    }

}           
【Java核心技術卷】深入了解Java的接口

6. 接口的繼承2

//3 接口的繼承(接口--接口的繼承)
//3-2 組合接口時的名字沖突

//原因:實作類中不允許出現方法簽名相同,而傳回類型不同的方法聲明。


interface I1 { 
    void f(); 
}
interface I2 { 
    int f(int i); 
}
interface I3 { 
    int f(); 
}

class C { 
    public int f() { 
        return 1; 
    } 
}    //virtual

class C2 implements I1, I2 {
    public void f() {}
    public int f(int i) {
        return 1; 
    }         // overloaded, virtual
}

class C3 extends C implements I2 {
    public int f(int i) { 
        return 1; 
    }         // overloaded, virtual
}

class C4 extends C implements I3 {          //override, reuse
    // Identical, no problem:
    public int f() { 
        return 1; 
    }
}

// Methods differ only by return type:

//! class C5 extends C implements I1 {}

//! interface I4 extends I1, I3 {}
           

接口并不繼承Object的虛方法

接口方法表的設定也相對比較簡單

7. 面向接口程式設計,而不是面向類程式設計

//優點:
//1、減少了耦和
//2、可維護
//3、可擴充


/*
 * 産品的抽象接口
 */
interface IProduct {
    //擷取産品
    String get();
}


//ProductA實作IProduct接口
class ProductA implements IProduct{
    //實作接口中的抽象方法
    public String get() {
        return "ProductA生産完畢!";
    }
}


//ProductB實作IProduct接口
class ProductB implements IProduct {
    // 實作接口中的抽象方法
    public String get() {
        return "ProductB生産完畢!";
    }
}


//ProductC實作IProduct接口
class ProductC implements IProduct{
    //實作接口中的抽象方法
    public String get() {
        return "ProductC生産完畢!";
    }
}


//工廠類
class Factory {
    // 根據客戶要求生産産品
    public static IProduct getProduct(String name) {
        IProduct p = null;
        if (name.equals("ProductA")) {
            p = new ProductA();
        } else if (name.equals("ProductB")) {
            p = new ProductB();
        }
        //} else if (name.equals("ProductC")) {
        //p = new ProductB();
        //}
        return p;
    }

}


//具有通用性程式
public class Main {
    public static void main(String[] args) {
        // 客戶要求生産ProductA
        IProduct p = Factory.getProduct("ProductA");
        System.out.println(p.get());
        // 客戶要求生産ProductB
        p = Factory.getProduct("ProductB");
        System.out.println(p.get());
        // 客戶要求生産ProductC
        //p = Factory.getProduct("ProductC");
        //System.out.println(p.get());
    }

}
           
【Java核心技術卷】深入了解Java的接口