在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
,因此就是預設為套件權限,這等於是將Action
中public
的方法縮小為套件權限,所以編譯失敗了!必須將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()");
}
}
但在設計上,你要思考一下:Some
與Other
定義的execute()
是否表示不同的行為?
如果表示不同的行為,那麼Service
在實作時,應該有不同的方法實作,那麼Some
與Other
的execute()
方法就得在名稱上有所不同,Service
在實作時才可以有兩個不同的方法實作。
如果表示相同的行為,那可以定義一個父介面,在當中定義execute()
方法,而Some
與Other
繼承該介面,各自定義自己的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之後,也代表了繼承父介面中有限制的實作。