多型(Polymorphism)
Polymorphism 這個單字在生物學的定義,是指一個物種的同一種群中存在兩種或多種明顯不同的表型。且必須同一時間在同一棲息地中出現。
套用在程式語言,則是 Object 類別(物種)的某自定義類別(種群)中,存在多種不同的表型(函式 / 方法)。
也就是說多型的特性,可讓同名程式,根據使用的不同類別,而有不同的行為。
延續 Animal 類別的例子,同樣 sayHi()
方法,Cat 跟 Fish 各自繼承後,卻實作不同邏輯,這就是多型的特性。如此一來,只要確保「子類 is a 父類」,不需要重複在子類定義,也能知道他們做一樣的事。
abstract class Animal {
abstract void sayHi();
// code ...
}
public class Cat extends Animal {
@Override
void sayHi() {
System.out.println("Meow");
}
// code ...
}
public class Fish extends Animal {
@Override
void sayHi() {
System.out.println("Blub...");
}
// code ...
}
介面 / 接口(interface)
不論實體或抽象類別,「繼承」這項行為本身就對特性存在某些限制,前面說到繼承是「is a」的概念,Cat is an Animal、Fish is an Animal,但當我們想寫一個 Robot 類別實作 sayHi()
,這時我們讓它繼承 Animal 類別,@Override
sayHi()
,所以 Robot is an Animal,咦?怎麼怪怪的?
假如僅能繼承,就會出現如此荒謬的結論,應對繼承以外的情境,有 介面(interface) 這個抽象型別,做更彈性的定義。
繼承之於「is a」隸屬的概念,介面強調「has a」擁有什麼樣的行為,用來定義抽象方法,讓類別以 implements
(實作)使用,而這個概念會被稱作複合(Composition)。一個類別只能繼承一個類別,但能實作多個介面。
interface soundMaker {
abstract void sayHi();
}
abstract class Animal {
// code ...
}
abstract class Machine {
// code ...
}
public class Cat extends Animal implements soundMaker {
@Override
public void sayHi() {
System.out.println("Meow");
}
}
public class RobotA implements soundMaker {
@Override
public void sayHi() {
System.out.println("I'm RobotA");
}
}
public class RobotB extends Machine implements soundMaker {
@Override
public void sayHi() {
System.out.println("I'm RobotB");
}
}
成員定義與修飾子
節錄翻譯介面在 Java 的定義:
介面是一種參考型別,類似於類別,只能包含方法簽名(就是方法)、常數宣告、巢狀類別(在一個「類別」中定義「另外一個類別」的行為),方法只能有 default 方法跟 static 方法,能被類別
implement
實作或其他介面extends
繼承,不能被實例化。
可以把介面視為一種特別的抽象類別,定義屬性及方法,但介面用途是拿來「實作」,屬性預設 public static final
(常數),方法會預設 public abstract
,SE8 後 default
能寫實例方法(解決擴展早期介面方法,導致早期實作了介面的類別將新方法視為實例方法,而編譯錯誤的問題),但通常還是定義抽象方法居多。
interface flyer {
// 屬性僅能宣告常數,預設 public static final
String name = "defaultName";
// public 只能寫 abstract 方法,預設 public abstract
public abstract void land();
// SE8 後 default 能寫實例方法
default void fly() {
System.out.println("default 可以寫實例方法");
}
}
// 還原 SE8 前不能使用 default 方法的情形
// 以前定義好的介面,介面放置在檔案 a
interface EarlyInterface {
// 當時定義好的方法
public abstract void methodforClassA();
// 很久以後 classB 要使用才定義
public abstract void methodforClassB();
}
// 定義介面當時的類別,類別放置在檔案 b
class ClassA {
@Override
public void methodforClassA();
}
// 很久以後才定義的 classB,類別放置在檔案 c
class ClassB {
@Override
public void methodforClassB();
}
// 如果沒有改寫 methodforClassA(),就會造成編譯問題
抽象類別 v.s 介面
抽象類別跟介面看似都可讓類別重新定義屬性與方法,但應用情境不同:
抽象類別:父類希望將實現細節留給子類,希望確保所有子類都滿足父類的結構
介面:希望兩個系統 / 類別 / 組件相互溝通時
介面可看作抽象方法的一種,與抽象類別本體定義方式相同,差異在內部成員定義方式:
abstract class | interface | |
---|---|---|
本體 | 權限限制 public,不能用 static、final | 同 abstract class |
內部成員 - 屬性 |
權限不限制、 可 static、final |
僅能宣告常數 (public static final) |
內部成員 - 實例方法 |
權限不限制、 關鍵字可 static、final |
權限限 default、 關鍵字預設 static 不可 final |
內部成員 - 抽象方法 |
僅能以 public abstract 修飾 | 同 abstract class |