Ch.12 介面、套件

12-1 介面

為了要操作物件,您必須得知物件上所公開的(public)操作介面,即使是繼承關係上的父子類別,透過父類別型態所宣告的名稱,也只能操作父類別中有宣告的公開方法,對於子類別中所新定義的方法並無法操作。

透過定義抽象方法來規範公開的操作介面只是一個解決的方法,由於在 Java 中只能單一繼承,也就是一次只能繼承一個類別,對於非該類別繼承體系的物件,定義抽象方法並無法解決問題。

在 Java 中介面( interface )用來規範公開的操作介面,一個實作介面的某類別,必須實作介面中所有已規範的操作方法,一個類別可以實作多個介面,這表示一個類別可以身兼多個角色與職責。

• 定義介面

考慮您有一個物件容器,您可以將「任何」物件儲存至該容器中,您希望容器被加入物件時,都可以在螢幕上顯示一段招呼語,而任何物件被從容器移除時,也可以顯示一段訊息顯示物件已經離開容器。

圖12-1 加入或離開容器時都會顯示訊息

注意需求所說的是「任何」物件,所以您無法使用抽象類別事先定義好抽象方法來滿足這個需求,因為您並無法得知被加入容器的物件是屬於哪一個類別。

在 Java 中您可以使用介面(interface)來解決這個需求,定義介面的的關鍵字是 interface,語法則如下所示:

[modifier1] interface interface_identifier {
    [modifier2] type method_identifier(type param..);
}
                            

其中:

  • modifier1 被使用來決定此介面相對於其它類別或介面的存取權。
  • interface 關鍵字告訴編譯器,這是個介面宣告。
  • interface_identifier 是您要取的介面名稱。
  • modifier2 是介面中定義的方法之權限設定,如果沒有定義,預設是 public。
  • type 用來定義方法的返回值,method_identifier 是方法名稱,param 用來定義參數列型態與名稱。

以實際的例子來看看如何定義一個介面,如程式碼 12-1 所示:

public interface ContainerListener {
    public void doHello();
    public void doGoodbye();
}
                            
程式碼 12-1 ContainerListener.java

在第 2 行與第 3 行只定義了方法權限、返回值型態、方法名稱與空的參數列,而不用實作方法本體,撰寫介面時也是以*.java 檔案撰寫,編譯過後也是產生*.class 檔案。

表面上看來,介面好像完全沒有任何實作本體的方法集合,類似於類別本體都是抽象方法的抽象類別,但事實並不是如此,「繼承某抽象類別的類別必定是該類別的一個子類」,但「實作某介面的類別並不被歸屬於哪一類」,一個類別上可以實作多個介面。

圖12-1 介面就像是物件所擁有的角色

• 實作介面

在定義類別時,可以一併使用 implements 關鍵字來指定要實作的介面,例如設計兩個類別,它們都實作了ContainerListener,會在被加入某個容器或被移除時顯示訊息,直接以程式碼 12-2、12-3 作為示範:

public class Some implements ContainerListener {
    public void doHello() {
        System.out.println("Some 物件被加入...");
    }

    public void doGoodbye() {
        System.out.println("Some 物件被移除...");
    }
}
                            
程式碼 12-2 Some.java
public class Other implements ContainerListener{
    public void doHello() {
        System.out.println("Other 物件被加入...");
    }

    public void doGoodbye() {
        System.out.println("Other 物件被移除...");
    }
}
                            
程式碼 12-3 Other.java

可以看到 Some 與 Other 兩個不同的類別,都以 implements 實作了 ContainerListener 介面,它們都必須遵守 ContainerListener 介面的規範:實作 doHello() 與doGoodbye()兩個方法。

當您取得實作介面的某個物件之後,您可以將它的操作介面轉換為所實作的介面,如此就可以使用介面上所規範的方法來操作物件,例如程式碼 12-4 實作一個簡單的容器,物件的加入或移除都會呼叫 doHello()與 doGoodbye()方法。

public class SimpleContainer {
    private Object[] objArr;
    private int index = 0;

    // 預設 10 個物件空間
    public SimpleContainer() {
        objArr = new Object[10];
    }
    public SimpleContainer(int capacity) {
        objArr = new Object[capacity];
    }
    // 加入物件
    public void add(Object o) {
        // 轉換操作介面
        ContainerListener listener =
                (ContainerListener) o;
        // 呼叫介面上規範的方法
        listener.doHello();
        objArr[index] = o;
        index++;
    }
    // 移除物件
    public void remove(int i) {
        // 轉換操作介面
        ContainerListener listener =
                (ContainerListener) get(i);
        objArr[i] = null;
        // 呼叫介面上規範的方法
        listener.doGoodbye();
    }
    public int length() {
        return index;
    }
    public Object get(int i) {
        return objArr[i];
    }
    public static void main(String[] args) {
        SimpleContainer container =
                new SimpleContainer(4);
        // 加入物件
        container.add(new Some());
        container.add(new Other());
        container.add(new Some());
        container.add(new Other());
        System.out.println();
        // 移除物件
        container.remove(0);
        container.remove(1);
        container.remove(2);
        container.remove(3);
    }
}
                            
程式碼 12-4 SimpleContainer.java

執行結果如下所示:

圖12-3 程式碼 12-4 執行結果

雖然 Some 與 Other 兩個是不同的類別,但由於它們都實作了 ContainerListener 介面,因而在 SimpleContaner 類別的add()與 remove()方法中,都可以在轉換操作介面之後,順利的操作 Some 物件與 Other 物件的 doHello() 與doGoodbye()方法。

圖12-4 利用介面規範操作方法

• 介面的繼承

介面也可以進行繼承的動作,同樣也是使用"extends"關鍵字,例如:

public interface 名稱 extends 介面 1, 介面 2 {
    // ...
}
                            

不同於類別的是,介面可以同時繼承多個介面,如果有一個A 介面繼承了 B、C 介面,則實作 A 介面的類別,對於 B、C 介面中規範的方法也必須一併實作。