介面的預設


在Java中,使用interface來定義抽象的行為外觀,方法可宣告為public abstract。例如:

public interface Swimmer {
    public abstract void swim();
}

介面中的方法沒有實作時,一定得是公開且抽象,為了方便,你也可以省略public abstract

public interface Swimmer {
    void swim();  // 預設就是public abstract
}

編譯器會自動幫你加上public abstract。由於預設一定是public,因此認證考試上經常會出這個題目:


public class Main {
    public static void main(String[] args) {
        Action action = new Some();
        action.execute();
    }
}

interface Action {
    void execute();
}

class Some implements Action {
    void execute() {
        System.out.println("作一些服務");
    }
}

「請問你執行結果為何?」這個問題本身就是個陷阱,根本無法編譯成功,因為Action中定義的execute()其實預設為public abstract,而Some類別在實作execute()方法時,沒有撰寫public,因此就是預設為套件權限,這等於是將Actionpublic的方法縮小為套件權限,所以編譯失敗了!必須將Some類別的execute()設為public才可通過編譯。

從JDK8開始,interface中的方法可以有限制地實作,這是為了支援Lambda而擴充的新特性,之後在談Lambda時會再介紹。

interface中,可以定義常數。例如:

package cc.openhome;

public interface Action {
    public static final int STOP = 0;
    public static final int RIGHT = 1;
    public static final int LEFT = 2;
    public static final int UP = 3;
    public static final int DOWN = 4;
}

Java中很常見到於介面中定義這類常數,稱為列舉常數,這讓程式撰寫清晰一些。例如:

package cc.openhome;

import static java.lang.System.out;

public class Game {
    public static void main(String[] args) {
        play(Action.RIGHT);
        play(Action.UP);
    }    

    public static void play(int action) {
        switch(action) {
            case Action.STOP:
                out.println("播放停止動畫");
                break;
            case Action.RIGHT:
                out.println("播放向右動畫");
                break;
            case Action.LEFT:
                out.println("播放向左動畫");
                break;
            case Action.UP:
                out.println("播放向上動畫");
                break;
            case Action.DOWN:
                out.println("播放向下動畫");
                break;
            default:
                out.println("不支援此動作");
        }
    }
}

想想看,如果將上面這個程式改為以下,哪個在維護程式時比較清楚呢?

...

    public static void main(String[] args) {
        play(1);     // 數字比較清楚?還是列舉常數比較清楚?
        play(3);
    }

  
    public static void play(int action) {
        switch(action) {
            case 0:   // 數字比較清楚?還是列舉常數比較清楚?
                out.println("播放停止動畫");
                break;
            case 1:
                out.println("播放向右動畫");
                break;
            case 2:
                out.println("播放向左動畫");
                break;
                略...
            default:
                System.out.println("不支援此動作");
        }
    }

...
   

事實上,在interface中,也只能定義public static final的列舉常數,為了方便,也可以如下撰寫:

public interface Action {
    int STOP = 0;
    int RIGHT = 1;
    int LEFT = 2;
    int UP = 3;
    int DOWN = 4;
}
  

編譯器會幫你展開為public static final,所以在介面中列舉常數,一定要使用=指定值,否則就會編譯錯誤。

要在類別中定義列舉常數也是可以的,不過就一定要明確寫出public static final。實際上,在JDK5之後新增列enum特性,使用常數來進行列舉已不再鼓勵,建議使用enum列舉,這之後還會介紹。

類別可以實作兩個以上的介面,如果有兩個介面都定義了某方法,而實作兩個介面的類別會怎樣嗎?程式面上來說,並不會有錯誤,照樣通過編譯:

interface Some {
    void execute();
    void doSome();
}
interface Other {
    void execute();
    void doOther();
}
public class Service implements Some, Other {
    @Override
    public void execute() {
        System.out.println("execute()");
    }
    @Override
    public void doSome() {
        System.out.println("doSome()");
    }
    @Override
    public void doOther() {
        System.out.println("doOther()");
    }
}

但在設計上,你要思考一下:SomeOther定義的execute()是否表示不同的行為?

如果表示不同的行為,那麼Service在實作時,應該有不同的方法實作,那麼SomeOtherexecute()方法就得在名稱上有所不同,Service在實作時才可以有兩個不同的方法實作。

如果表示相同的行為,那可以定義一個父介面,在當中定義execute()方法,而SomeOther繼承該介面,各自定義自己的doSome()doOther()方法:

import static java.lang.System.out;

interface Action {
    void execute();
}
interface Some extends Action {
    void doSome();
}
interface Other extends Action {
    void doOther();
}
public class Service implements Some, Other {
    @Override
    public void execute() {
        out.println("execute()");
    }
    @Override
    public void doSome() {
        out.println("doSome()");
    }
    @Override
    public void doOther() {
        out.println("doOther()");
    }
}

介面可以繼承別的介面,也可以同時繼承兩個以上的介面,同樣也是使用extends關鍵字,這代表了繼承父介面的行為,在JDK8之後,也代表了繼承父介面中有限制的實作。