標籤:並且 cas 表達 opp cleanup 樣本 何事 assign 額外
7.1 組合文法
1)組合即 將對象引用置於新類中
2)每一個非基本類型的對象都有一個toString()方法,而且當編譯器需要一個String而你卻只有一個對象時,該方法便會被調用。
3)初始化一個類中的對象引用有如下四種方式:
1.在定義對象的地方初始化,,意味著總能在調用構造器之前被初始化
2.在類的構造器中
3.就在正要使用這些對象之前,這種叫惰性初始化,這種可以減少額外的負擔
4.使用執行個體初始化
class Soap { private String s; Soap() { print("Soap()"); //2.在類的構造器中初始化 s = "Constructed"; } public String toString() { return s; }} public class Bath { private String // 1.在定義對象的地方初始化: s1 = "Happy", s2 = "Happy", s3, s4; private Soap castille; private int i; private float toy; public Bath() { print("Inside Bath()"); //2.在類的構造器中初始化: s3 = "Joy"; toy = 3.14f; castille = new Soap(); } // 4.執行個體初始化: { i = 47; } public String toString() { if(s4 == null) // 3.惰性初始化(Delayed initialization): s4 = "Joy"; return "s1 = " + s1 + "\n" + "s2 = " + s2 + "\n" + "s3 = " + s3 + "\n" + "s4 = " + s4 + "\n" + "i = " + i + "\n" + "toy = " + toy + "\n" + "castille = " + castille; } public static void main(String[] args) { Bath b = new Bath(); print(b); }} /* Output:Inside Bath()Soap()s1 = Happys2 = Happys3 = Joys4 = Joyi = 47toy = 3.14castille = Constructed
7.2繼承文法
1)當建立一個類時,總是在繼承,因此,除非已明確指出要從其他類中繼承,否則就是在隱式地從Java的標準根類Object進行繼承。
2)繼承關鍵字extends:繼承會自動得到基類中所有的域(什麼是域)和方法。
3)調用另外一個類的main函數的方式與調用另外一個類的普通靜態函數相同,即類名.main(args);,args可以是主調用類從命令列獲得的參數,也可以是其他任意的String數組。
4)可以為每個類都建立一個main方法。這種在每個類中都設定一個main方法的技術可使每個類的單元測試都變得簡單易行。而且在完成單元測試之後,也無需刪除main(),可以留待下次測試。
5)即使一個類只具有包存取權限,其public main()仍然是可以訪問的。(還沒證實?)
6)為了繼承,一般的規則是將所有的資料成員都指定為private,將所有的方法指定為public(protected方法也可以藉助匯出類來訪問,後面說到)。
7)Java用 super 關鍵字表示超類(父類)。運算式super.fun();可以調用父類中的函數(此處是調用函數fun())。
7.2.1 初始化基類
注意:基類=父類;匯出類=子類。
1)當建立了一個匯出類的對象時,該對象包含了一個基類的子物件,該子物件被封裝在匯出類對象內部。
2)基類子物件的初始化:在構造器中調用基類構造器來執行初始化。在執行基類構造器之前,定義處初始化、執行個體初始化等均會被執行。 ※ Java會自動在匯出類的構造器中插入對基類構造器的調用
。
範例程式碼:
class Art { private String art = " test art.\n"; private String artS; { artS = " ART"; } Art() { print("Art constructor"+art+artS); }}class Drawing extends Art { private String draw = " test drawing.\n"; private String drawS; { drawS = " DRAW"; } Drawing() { print("Drawing constructor"+draw+drawS); }}public class Cartoon extends Drawing { public Cartoon() { print("Cartoon constructor"); } public static void main(String[] args) { Cartoon x = new Cartoon(); }} /* Output:Art constructor test art. ARTDrawing constructor test drawing. DRAWCartoon constructor
可以看出,構建過程是從基類“向外”擴散的,所以基類在匯出類構造器可以訪問它之前,就已經完成初始化了。當然,預設構造器也會逐層調度基類的構造器。
7.2.2 帶參數的構造器
編譯器可以自動調用預設的建構函式,是因為它們沒有任何參數。
但是如果沒有預設的基類建構函式,或者想調用一個帶參數的基類建構函式,必須使用關鍵字super顯示地編寫調用基類建構函式的語句,並且配以適當的參數列表。
如果基類沒有預設構造器(無參構造器),匯出類不顯式的調用基類的帶參構造器,則編譯器會報錯。
7.3 代理
代理是第三種複用代碼的關係,Java並沒有提供對它的直接支援。它是繼承和組合之間的中庸之道:
- 首先,我們需要將一個成員對象置於所要構造的類中(就像組合);
- 其次,我們需要在新類中暴露該成員對象的所有方法(就像繼承)或該成員對象的所有方法的某個子集。
範例程式碼:
public class SpaceShipControls { void up(int velocity) {} void down(int velocity) {} void left(int velocity) {} void right(int velocity) {} void forward(int velocity) {} void back(int velocity) {} void turboBoost() {}}public class SpaceShipDelegation { private String name; private SpaceShipControls controls = new SpaceShipControls(); public SpaceShipDelegation(String name) { this.name = name; } // Delegated methods: public void back(int velocity) { controls.back(velocity); } public void down(int velocity) { controls.down(velocity); } public void forward(int velocity) { controls.forward(velocity); } public void left(int velocity) { controls.left(velocity); } public void right(int velocity) { controls.right(velocity); } public void turboBoost() { controls.turboBoost(); } public void up(int velocity) { controls.up(velocity); } public static void main(String[] args) { SpaceShipDelegation protector = new SpaceShipDelegation("NSEA Protector"); protector.forward(100); }}
反例代碼:
public class SpaceShip extends SpaceShipControls{ private String name; public SpaceShip(String name) { this.name = name; } @Override public String toString() { return name; } public static void main(String[] args) { SpaceShip ship = new SpaceShip("NSEA Protector"); ship.foward(100); }}
SpaceShip並非真正的SpaceSbipControls類型,即便你可以“告訴”SpaceShip向前運動(forward())。更準確地講,SpaceShip包含SpaceShipControls,與此同時,SpaceShipControls的所有方法在SpaceShip中都暴露了出來(不懂哪裡暴露了)。上面的例子就可以解決。
7.4 結合使用組合和繼承
同時使用組合和繼承,並配以必要的構造器初始化,可以建立更加複雜的類。
7.4.1 確保正確清理
try{ //......}finally{ x.cleanup();}
上述代碼中的finally子句表示的是“無論發生什麼事,一定要為x調用cleanup()。”
在清理方法(dispose())中,必須注意對基類清理方法和成員對象清理方法的調用順序,以防某個子物件依賴於另外一個子物件的情形發生。
假若一個子物件要以另一個為基礎。通常,應採取與C++編譯器對它的“破壞器”採取的同樣的形式:首先完成與類有關的所有特殊工作(可能要求基礎類元素仍然可見),然後調用基礎類清除方法。
- 一般,採用與C++編譯器在其解構函式上所施加的形式:首先,執行類的所有特定的清理工作,其順序同產生順序相反(通常這就要求基類元素仍舊存活);然後調用基類的清理方法。
- 注意:除了記憶體以外,不能依賴記憶體回收行程去做任何事。如果需要進行清理,最好編寫自己的清理方法,但是不要使用finalize()。
7.4.2 名稱屏蔽
7.8 final 關鍵字7.8.1 資料
聲明資料為常量,可以是編譯時間常量,也可以是在運行時被初始化後不能被改變的常量。
- 對於基本類型,final 使數值不變;
- 對於參考型別(包含數組),final 使引用不變,也就不能引用其它對象,但是被引用的對象本身是可以修改的。
final int x = 1;x = 2; // cannot assign value to final variable ‘x‘final A y = new A();y.a = 1;
空白final:是指被聲明為final但又未給定初值的域。但空白final必須在構造器中用運算式賦值。例子如下:
class Poppet { private int i; Poppet(int ii) { i = ii; }}public class BlankFinal { private final int i = 0; // Initialized final private final int j; // Blank final private final Poppet p; // Blank final reference // Blank finals MUST be initialized in the constructor: public BlankFinal() { j = 1; // Initialize blank final p = new Poppet(1); // Initialize blank final reference } public BlankFinal(int x) { j = x; // Initialize blank final p = new Poppet(x); // Initialize blank final reference } public static void main(String[] args) { new BlankFinal(); new BlankFinal(47); }}
總之,必須在域的定義外或每個構造器中用運算式對fianl進行賦值。
7.8.2 方法
使用final方法的原因有兩個:
- 把方法鎖定,以防任何繼承類修改它的含義。這是出於設計的考慮:確保在繼承類中使方法行為保持不變,並且不會被覆蓋。
- 效率:類似於C++的inline機制,早期的虛擬機器需要,現在不需要了,故現在不需要使用final方法進行最佳化了。
綜上,僅當你想顯式地阻止覆蓋該方法時,才使該方法成為final的。
聲明方法不能被子類覆蓋。
private 方法隱式地被指定為 final,如果在子類中定義的方法和基類中的一個 private 方法簽名相同,此時子類的方法不是覆蓋基類方法,而是重載了。
final方法和private方法類似,區別在於,private只能在類內訪問,類外訪問不到,final方法可以在類外被訪問,但不可以重寫,可以使用該方法的功能但是不可以改變其功能
7.8.3 類
final置於類的定義之前表示該類不允許被繼承。這樣做的原因如下:
- 出於某種考慮,你對該類的設計永不需要做任何變動;
- 出於安全的考慮,你不希望它有子類。
Java編程思想 第七章