Thinking in Java 第七章 3-1
Thinking in Java第七章研讀3-1總結
問題引入:如何復用代碼
1.新的類是由現有類的對象所組成,方法稱為組合。(該方法只是復用了現有程序代碼的功能,而非他的形式)
2.按照現有類的類型創建新類。方法稱為繼承。(該方法無需改變現有類的形式,采用現有類的形式並在其中添加新代碼)
3.代理proxy
組合Demo(存在問題:對象引用的初始化)
1 package com.thxy.section.seven; 2 3 public class Compo { 4 public static void main(String[] args) {5 Bath bath = new Bath(); 6 System.out.println(bath); 7 8 } 9 } 10 11 class Soap { 12 private String s; 13 14 Soap() { 15 System.out.println("Soap()"); 16 s = "Constructed"; 17 } 18 19 @Override 20 public String toString() {21 return s; 22 } 23 } 24 25 class Bath { 26 private String s1 = "Happy"; 27 private String s2 = "Happy"; 28 private String s3, s4; 29 private Soap castille; 30 private int i; 31 private float toy; 32 33 Bath() { 34 System.out.println("Inside Bath()");35 s3 = "Joy"; 36 toy = 3.14f; 37 castille = new Soap(); 38 } 39 40 { 41 i = 47; 42 } 43 44 @Override 45 public String toString() { 46 if (s4 == null) { 47 s4 = "Joy"; 48 } 49 return 50 "s1=" + s1 + "\n" + "s2=" + s2 + "\n" + "s3=" + s3 + "\n" + "s4=" + s4 + "\n" + "toy=" + toy + "\n" + "castille=" + castille; 51 } 52 }
總結:
初始化引用對象可以在代碼中的如下位置
1.在定義對象的地方。這意味著它們總能在構造器被調用之前被初始化。
2.在類的構造器中
3.就在正要使用這些對象之前,這種方法稱為惰性初始化。
使用斷點調試執行過程
1.
由調試結果得知:
1.先初始化s4=Joy
2.初始化s1,s2
3.{i=47}初始化i
2.
由調試結果得知:
1.先初始化s3,toy
2.再初始化Soup引用對象
4.最後初始化s引用對象
問題:為什麽會立即初始化s4引用對象?
原著:當toString被調用時,它將填充s4的值,以確保所有的域在使用之時已被妥善初始化。是否這麽寫就錯誤了?通過debug調試jvm虛擬機將s4就初始化了。
繼承Demo
1 package com.thxy.section.seven; 2 3 public class Inherit { 4 public static void main(String[] ars) { 5 Detergent x = new Detergent(); 6 x.dilute(); 7 x.apply(); 8 x.scrub(); 9 x.foam(); 10 System.out.print(x); 11 } 12 } 13 14 class Cleanser { 15 private String s = "Cleanser."; 16 17 public void append(String a) { 18 s += a; 19 } 20 21 public void dilute() { 22 append("dilute()"); 23 } 24 25 public void apply() { 26 append("apply()"); 27 } 28 29 public void scrub() { 30 append("scrub()"); 31 } 32 33 @Override 34 public String toString() { 35 return s; 36 } 37 } 38 39 class Detergent extends Cleanser { 40 /* 41 覆蓋 42 */ 43 @Override 44 public void scrub() { 45 append("Detergent.scrub()"); 46 } 47 48 /* 49 新增 50 */ 51 public void foam() { 52 append("foam()"); 53 } 54 }
總結:
Cleanser.dilute()apply()Detergent.scrub()foam()
1.繼承,一般的規則是將所有數據成員都指定為private,將所有方法指定為public(protected成員也可以借助導出來類訪問)。
2.由於Detergent是由關鍵字extends從Cleanser導出,所以它可以在接口中自動獲取這些方法,盡管我們不能看到這些方法在Detergent中的顯示定義。因此,可以將繼承可以看做是對類的復用。
3.對基類中定義的方法對他進行修改是可行的(覆蓋/重寫)。
4.在繼承過程中,不一定非得使用基類的方法。可以在導出類中添加新方法。
5.對於導出來的對象不僅可以使用自己在類中定義的方法,而且還可以使用基類的方法。
基類和導出類構造方法(初始化)Demo
1 package com.thxy.section.seven; 2 3 public class Init { 4 public static void main(String[] args) { 5 Cartoon cartoon=new Cartoon(); 6 7 } 8 } 9 10 class Art { 11 Art() { 12 // super(); 13 System.out.println("Art constructor"); 14 } 15 } 16 17 class Drawing extends Art { 18 Drawing() { 19 // super(); 20 System.out.println("Drawing constructor"); 21 } 22 } 23 24 class Cartoon extends Drawing { 25 Cartoon() { 26 // super(); 27 System.out.println("Cartoon constructor"); 28 } 29 }
總結:
1.結果
Art constructor
Drawing constructor
Cartoon constructor
2.構造過程是從基類“向外”擴展的。
3.系統會默認地調用super()方法即使程序員不顯示調用。
4.系統會默認為類創建一個默認地構造器即使程序員不顯示寫構造器。
1 package com.thxy.section.seven; 2 3 public class Init { 4 public static void main(String[] args) { 5 Cartoon cartoon = new Cartoon(11); 6 Cartoon c = new Cartoon(); 7 8 } 9 } 10 11 class Art { 12 Art() { 13 // super(); 14 System.out.println("Art constructor"); 15 } 16 17 Art(int i) { 18 System.out.println("Art constructor" + i); 19 } 20 } 21 22 class Drawing extends Art { 23 Drawing() { 24 System.out.println("Drawing constructor"); 25 26 } 27 28 Drawing(int i) { 29 super(i); 30 System.out.println("Drawing constructor" + i); 31 } 32 } 33 34 class Cartoon extends Drawing { 35 Cartoon() { 36 System.out.println("Cartoon constructor"); 37 38 } 39 40 Cartoon(int i) { 41 super(i); 42 System.out.println("Cartoon constructor" + i); 43 } 44 }
總結:
1.結果
Art constructor11
Drawing constructor11
Cartoon constructor11
Art constructor
Drawing constructor
Cartoon constructor
2.如果程序員寫了帶參數的構造器系統將不會默認創建構造器。
3.如果想調用一個帶參數的基類構造器就必須用關鍵字super顯示地編寫調用基類構造器的語句。
4.註意:如果基類重寫了構造器導出類想調用一個帶參數的基類構造器就要顯示調用super語句;如果不寫則報錯因為導出類的構造器會默認調用super()方法。
中國中庸之道之代理Demo(繼承和組合的中庸之道)
example:太空船需要一個控制模塊
1 package com.thxy.section.seven; 2 3 public class Proxy { 4 public static void main(String[] args) { 5 SpaceShip spaceShip = new SpaceShip("NSEA Protector"); 6 spaceShip.forward(100); 7 } 8 } 9 10 class SpaceShipControls { 11 void up(int velocity) { 12 } 13 14 void down(int velocity) { 15 } 16 17 void left(int velocity) { 18 } 19 20 void right(int velocity) { 21 } 22 23 void forward(int velocity) { 24 System.out.println(this+" "+"forward" + " "+velocity); 25 } 26 27 void back(int velocity) { 28 } 29 30 void turboBoost() { 31 } 32 } 33 34 class SpaceShip extends SpaceShipControls { 35 private String name; 36 37 SpaceShip(String name) { 38 this.name = name; 39 } 40 41 @Override 42 public String toString() { 43 return name; 44 } 45 }
總結:
1.結果:NSEA Protector forward 100
2.SpaceShip並非真正的SpaceShipControls類型(按向上轉型地說SpaceShip對象是一種類型的SpaceShipControls?)(向上轉型時說法)中文翻譯很難懂。真正邏輯上SpaceShip中包含了SpaceShipControls;應該用組合才對。這裏就不適合用繼承因為邏輯亂了。
3.SpaceShipControls所有的方法在SpaceShip中暴露了。
需要解決的問題:1.SpaceShip和SpaceShip的關系 2.隱藏SpaceShipControls具體實現方法(結合實際想象我們國防內部技術實現不能透露,可是他的功能是可以暴露的)
邏輯優化Demo(采用單例模式)
1 package com.thxy.section.seven; 2 3 public class Proxy2 { 4 public static void main(String[] args) { 5 SpaceShip2 spaceShip2 = new SpaceShip2(); 6 spaceShip2.up(100); 7 spaceShip2.down(100); 8 spaceShip2.forward(100); 9 spaceShip2.back(100); 10 spaceShip2.turboBoost(); 11 12 } 13 } 14 15 class SpaceShipControls2 { 16 17 private SpaceShipControls2() { 18 19 } 20 21 /* 22 一條太空飛船中只有一個控制器 23 */ 24 private static SpaceShipControls2 spaceShipControls2 = new SpaceShipControls2(); 25 26 public static SpaceShipControls2 spaceShipControl() { 27 return spaceShipControls2; 28 } 29 30 void up(int velocity) { 31 System.out.println(this + " " + "up" + " " + velocity); 32 } 33 34 void down(int velocity) { 35 System.out.println(this + " " + "down" + " " + velocity); 36 } 37 38 void left(int velocity) { 39 System.out.println(this + " " + "left" + " " + velocity); 40 } 41 42 void right(int velocity) { 43 System.out.println(this + " " + "right" + " " + velocity); 44 } 45 46 void forward(int velocity) { 47 System.out.println(this + " " + "forward" + " " + velocity); 48 } 49 50 void back(int velocity) { 51 System.out.println(this + " " + "back" + " " + velocity); 52 } 53 54 void turboBoost() { 55 System.out.println(this + " " + "turboBoost"); 56 } 57 } 58 59 class SpaceShip2 { 60 private String name; 61 private SpaceShipControls2 spaceShipControls2 = SpaceShipControls2.spaceShipControl(); 62 63 SpaceShip2() { 64 this.name = name; 65 } 66 67 @Override 68 public String toString() { 69 return name; 70 } 71 72 void up(int velocity) { 73 spaceShipControls2.up(velocity); 74 } 75 76 void down(int velocity) { 77 spaceShipControls2.down(velocity); 78 } 79 80 void left(int velocity) { 81 spaceShipControls2.left(velocity); 82 } 83 84 void right(int velocity) { 85 spaceShipControls2.right(velocity); 86 } 87 88 void forward(int velocity) { 89 spaceShipControls2.forward(velocity); 90 } 91 92 void back(int velocity) { 93 spaceShipControls2.up(velocity); 94 } 95 96 void turboBoost() { 97 spaceShipControls2.turboBoost(); 98 } 99 100 }
總結:
1.結果:
com.thxy.section.seven.SpaceShipControls2@4554617c up 100
com.thxy.section.seven.SpaceShipControls2@4554617c down 100
com.thxy.section.seven.SpaceShipControls2@4554617c forward 100
com.thxy.section.seven.SpaceShipControls2@4554617c up 100
com.thxy.section.seven.SpaceShipControls2@4554617c turboBoost
2.通過上述的實現中可以很好理清邏輯:廣泛的說每條太空船都有其名稱和專屬的控制器:控制器的具體如何實現都隱藏在控制器中巧妙地解決了邏輯問題。
3.從上述的實現中可以很好理解到向上轉型中當選擇使用繼承還是組合時考慮自己是否真的很需要向上轉型嗎?現實中很多對象都是包含和被包含的關系這時通常不需要向上轉型可以使用組合或許更好的解決邏輯關系。但是包含與被包含關系也有部分要利用繼承擴張。毛主席曾經說過具體問題具體分析,實事求是是不無道理的。
神奇的名稱屏蔽(類和類之間的重載)
重載機制的條件:
1.以參數區分重載方法
2.以返回值區分重載方法(行不通因為有時並不關心返回值只關心方法是如何實現的)
總的來說構成重載機制方法名一致;參數類型和個數和返回值其中一個不同。
1 package com.thxy.section.seven; 2 3 public class Overloading { 4 public static void main(String[] args) { 5 Bart bart = new Bart(); 6 bart.doh(new MilHouse(10)); 7 bart.doh(‘c‘); 8 bart.doh(0.01f); 9 10 } 11 } 12 13 class Homer { 14 /* 15 重載 16 */ 17 void doh(char c) { 18 System.out.println(c); 19 } 20 21 void doh(float f) { 22 System.out.println(f); 23 } 24 } 25 26 class MilHouse { 27 private int i; 28 29 MilHouse(int i) { 30 this.i = i; 31 } 32 33 @Override 34 public String toString() { 35 return "" + i; 36 } 37 } 38 39 class Bart extends Homer { 40 /* 41 重載 42 */ 43 void doh(MilHouse milHouse) { 44 System.out.println(milHouse); 45 } 46 }
總結:
1.結果
10
c
0.01
2.可以看出在基類定義的重載方法doh(xx)方法,在導出類也定義了重載方法doh(xx)方法。在導出類中不僅可以調用自己的重載方法,也可以調用基類的重載方法。
3.由2知雖然Bart引入了一個新的重載方法,但是在Bart中Homer的所有重載方法都是可用的。
4.在程序員不留意重載而並非重寫了該方法時使用Java SE5新增的@Override註解可以大大減少閱讀的困難性。
5.有大部分人都認為重載和重寫的區別是重載一定是發生在同一類中的,重寫是發生在不同類中的。其實這種說法是片面的。當導出類繼承了基類也可以重載基類的方法
並且可以調用基類的方法。我想他們那些人會考慮導出類繼承了基類就相當於導出類是基類的一種類型也相當在一個類中。
備註:本人大二在校生在研讀Thinking in Java這本經典書,由於英語水平有限不能讀原版的Thinking in Java英文版所以只能讀中文版,但是中文翻譯讀起來也是夠嗆。如果我對其中知識點理解有誤請指正。謝謝。上面有殘留一個問題請大神指教。(原著:當toString被調用時,它將填充s4的值,以確保所有的域在使用之時已被妥善初始化。是否這麽寫就錯誤了?通過debug調試jvm虛擬機將s4就初始化了。)請將你們答案寫在下方留言!!!
Thinking in Java 第七章 3-1