面向過程語言靠複製程式碼複用;Java 物件導向有兩大機制:
class Composition {
private String s;
Composition() {
System.out.println("Composition()");
s = new String("hello");
}
public String toString() { return s; }
}
public class Test {
Composition c; // 物件參考
int i;
public void show() {
System.out.println("int = " + i);
System.out.println("Composition = " + c);
}
public static void main(String[] args) {
Test t = new Test();
t.show();
}
}
非基本型別參考預設 null,使用前必須初始化,否則 NullPointerException。每個物件皆有 toString() 供字串轉換。
關鍵字 extends。子類別自動獲得父類別所有成員。所有類別隱式繼承 Object。
class Base {
int i = 10;
public void show() { System.out.println("base method"); }
public static void main(String[] args) { new Base().show(); }
}
class Derived extends Base {
public void show() { // 覆寫
System.out.println("derived method");
super.show(); // 呼叫父類別方法
}
public void newMethod() { // 新增方法
System.out.println(i); // 存取父類別欄位
}
public static void main(String[] args) {
Derived d = new Derived();
d.show();
d.newMethod();
}
}
super()。
class Base { Base() { System.out.println("base constructor"); } }
class Derived extends Base {
Derived() {
// super(); 自動插入
System.out.println("derived constructor");
}
public static void main(String[] args) { new Derived(); }
}
編譯器不會自動呼叫,必須在子類別建構子第一行顯式呼叫 super(參數)。
class Base { Base(String s) { System.out.println(s); } }
class Derived extends Base {
Derived() {
super("base constructor"); // 必須第一行
System.out.println("derived constructor");
}
}
實務上常同時使用。選擇準則:
子類別可存取,同套件其他類別為 friendly (package-private)。
繼承支援在不影響父類別程式碼下新增功能。
子類別可當作父類別使用,任何傳給父類別的訊息皆可傳給子類別。
class Base {
protected void show() { System.out.println("base"); }
protected void getSomeone(Base b) { b.show(); }
}
class Derived extends Base {
public static void main(String[] args) {
Derived d = new Derived();
d.getSomeone(d); // Derived 傳入 Base 參數合法
}
}
編譯器看到 Base b 參數,實際傳入 Derived 合法,因子類別是父類別的一種。此轉型稱為向上轉型,安全(特殊→通用)。
class Base {
public void methodOne() { System.out.println("Base.methodOne"); }
public void methodTwo() { System.out.println("Base.methodTwo"); }
}
class Derived extends Base {
public void methodOne() { System.out.println("Derived.methodOne"); }
public void methodTwo() { System.out.println("Derived.methodTwo"); }
public void methodNew() { System.out.println("Derived.methodNew"); }
public static void main(String[] args) {
Base d = new Derived(); // 向上轉型
d.methodOne(); // 輸出 Derived.methodOne (動態繫結)
d.methodTwo(); // 輸出 Derived.methodTwo
// d.methodNew(); // 編譯錯誤:Base 無此方法
}
}
向上轉型會「遺失」子類別特有方法,但多型確保覆寫方法正確派發。
子類別是父類別的超集,向上轉型安全,是多型運作基石。
final、有初值。編譯期可計算,減少執行負擔。final int x = (int)(Math.random()*20))。static final:類別常數,唯一儲存空間,不可改變。final:參考不可變,但物件內容可修改。宣告 final 但不立即初始化,建構子中必須初始化。兼具不變性與靈活性(每個實例可有不同常數值)。
final 效果有限)。private 方法隱性為 final(無法覆寫)。在 private 加 final 無意義。
禁止被繼承。資料成員可自由選擇 final 與否。
class Base {
int i = 10; int j;
Base() { show("i=" + i + ", j=" + j); j = 20; }
static int m = show("static Base.m init");
static int show(String s) { System.out.println(s); return 30; }
}
class Derived extends Base {
int k = show("Derived.k init");
Derived() { show("k=" + k); show("j=" + j); }
static int n = show("static Derived.n init");
public static void main(String[] args) {
show("Derived main");
new Derived();
}
}
static Base.m init static Derived.n init Derived main i=10, j=0 Derived.k init k=30 j=20
組合複用實作,繼承複用介面。組合彈性高,繼承支援向上轉型與多型。建議多用組合,必要時繼承。