1. 組合語法
在新的類中產生現有類的對象,這種方法稱爲組合,該方法只是複用了現有程序代碼的功能,而非它的形式,例如:
class Test1 { public void sayHello() { System.out.println("Hello World"); } } class Test2 { private Test1 t1 = new Test1(); public void getString() { t1.sayHello(); } } public class Main { public static void main(String[] args) { Test2 t2 = new Test2(); t2.getString(); } }
每一個非基本類型的對象都有一個toString()方法,而且當編譯器需要一個String而卻只有一個對象時,該方法便會被調用,例如:
class Test1 { public String toString() { return "Hello World"; } } class Test2 { private Test1 t1 = new Test1(); public void getString() { System.out.println("Say: " + t1); } } public class Main { public static void main(String[] args) { Test2 t2 = new Test2(); t2.getString(); } }
2. 繼承語法
繼承是所有OOP語言和Java語言不可缺少的組成部分。當創建一個類時,如果沒有明確指出要從其他類中繼承,那麼就是在隱式地從Java標準根類Object進行繼承。在繼承過程中,需要用關鍵字extends實現,例如:
class Test1 { public void sayHello() { System.out.println("Hello World"); } public void sayBye() { System.out.println("Bye"); } } class Test2 extends Test1 { public void sayHello() { super.sayHello(); sayBye(); } } public class Main { public static void main(String[] args) { Test2 t2 = new Test2(); t2.sayHello(); } }
由於Test2是由關鍵字extends從Test1導出的,它可以自動獲得Test1中非private的方法,因此,可以將繼承視作是對類的複用。修改基類中定義的方法是可行的,Java用super關鍵字表示超類,可以在導出類中使用super來訪問基類版本的方法。
如果一個程序中含有多個類,可以爲每個類設置一個main()方法,這種技術可使每個類的單元測試變得簡便。只有命令行所調用的那個類的main()方法會被調用,比如命令行是"java Test1",那麼Test1.main()將會被調用,即使不是public類,public main()方法仍然會被調用,例如:
class Test1 { public void sayHello() { System.out.println("Hello World"); } public void sayBye() { System.out.println("Bye"); } public static void main(String[] args) { Test1 t1 = new Test1(); t1.sayHello(); t1.sayBye(); } } class Test2 extends Test1 { public void sayHello() { super.sayHello(); sayBye(); } public static void main(String[] args) { Test2 t2 = new Test2(); t2.sayHello(); t2.sayBye(); } } public class Main { public static void main(String[] args) { Test2 t2 = new Test2(); t2.sayHello(); } }
導出類就像是一個與基類具有相同接口的新類,或許還有一些額外的方法和域,當創建一個導出類的對象時,該對象包含了一個基類的子對象,子對象被包裝在導出類對象內部,並會被恰當初始化,例如:
class Test1 { public Test1() { System.out.println("Test1 constructor"); } } class Test2 extends Test1 { public Test2() { System.out.println("Test2 constructor"); } } public class Main { public static void main(String[] args) { Test2 t2 = new Test2(); } }
執行結果是先後輸出"Test1 constructor"和"Test2 constructor",可以發現構建過程是從基類向外擴散的,所以基類在導出類構造器可以訪問它之前,就已經完成了初始化,即使不爲Test1創建構造器,編譯器也會合成一個默認的構造器。
3. 代理
除了組合和繼承外,代理是第三種關係,介於繼承與組合之間。如果將一個成員對象置於所要構造的類中,與此同時我們在新類中暴露了該成員對象的所有方法以,例如:
public class SpaceShipControls { void up(int velocity) {} void down(int velocity) {} } public class SpaceShip extends SpaceShipControls { public static void main(String[] args) { SpaceShip ship = new SpaceShip(); ship.up(100); } }
SpaceShip包含SpaceShipControls,與此同時,SpaceShipControls的所有方法在SpaceShip中都暴露了出來,代理解決了此問題,例如:
public class SpaceShipControls { void up(int velocity) {} void down(int velocity) {} void reboot() {} } public class SpaceShip extends SpaceShipControls { private SpaceShipControls controls = new SpaceShipControls(); public void up(int velocity) { controls.up(velocity); } public void down(int velocity) { controls.down(velocity); } public static void main(String[] args) { SpaceShip ship = new SpaceShip(); ship.up(100); } }
4. final
根據上下文環境,Java的關鍵字final的含義存在細微的區別,但通常指的是“無法改變的”。不想做改變可能出於兩種理由:設計或效率。
許多編程語言都有某種方法告知編譯器一塊數據是恆定不變的,比如一個永不改變的編譯時常量或一個在運行時被初始化的值,而不希望它被改變。對於編譯期常量的情況,常量必須是基本數據類型,並且以關鍵字final表示,在對這個常量進行定義的時候,必須對其進行賦值。一個既是static又是final的域只佔據一段不能改變的存儲空間。當對對象引用而不是基本類型運用final時,表示final使引用恆定不變,一旦引用被初始化指向一個對象,就無法再指導它改爲指向另一個對象,然而對象自身卻是可以被修改的,例如:
class Value { int i; public Value(int i) { this.i = i; } } public class FinalData { private final int i = 10; private final Value v = new Value(20); public static void main(String[] args) { FinalData fd = new FinalData(); // Error : fd.i++ fd.v.i++; System.out.println(fd.i); System.out.println(fd.v.i); } }
Java允許生成空白fina。所謂空白final是指被聲明爲final但又未給定初值的域,無論什麼情況,編譯器都確保空白final在使用前必須被初始化,但是,空白final在關鍵字final的使用上提供了更大的靈活性,例如:
class Poppet { private int i; Poppet(int i) { this.i = i; } } public class BlankFinal { private final int i; private final Poppet p; public BlankFinal(int x) { i = x; p = new Poppet(x); } public static void main(String[] args) { new BlankFinal(10); } }
Java允許在參數列表中以聲明的方式將參數指明爲final,這意味着你無法在方法中更改參數引用所指向的對象,例如:
class Gizmo { public void spin() {} } public class FinalArguments { void with(final Gizmo g) { // Error : g = new Gizmo(); } void without(Gizmo g) { g = new Gizmo(); g.spin(); } public static void main(String[] args) { FinalArguments fa = new FinalArguments(); fa.without(null); fa.with(null); } }
使用final方法可以把方法鎖定,防止任何繼承類修改它的含義。類中所有的private方法都隱式地指定爲final的,可以對private方法添加final修飾詞,但這並不能給方法增加任何額外含義,例如:
class Test1 { final void f() {} } class Test2 extends Test1 { } public class Main { public static void main(String[] args) { Test2 t = new Test2(); t.f(); } }
當將某個類整體定義爲final時,表示不能繼承該類,例如:
final class Test { void f() {} } public class Main { public static void main(String[] args) { Test t = new Test(); t.f(); } }
final類的域可以根據個人意願選擇爲是或不是final,然後final類中所有的方法都隱式指定爲是final的,因爲無法覆蓋它們。