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就初始化了。)请将你们答案写在下方留言!!!
原文地址:https://www.cnblogs.com/KYAOYYW/p/10590441.html