复合和继承
这里举一个继承的初始化例子
// reuse/Detergent.java // (c)2017 MindView LLC: see Copyright.txt // We make no guarantees that this code is fit for any purpose. // Visit http://OnJava8.com for more book information. // Inheritance syntax & properties class Cleanser { private String s = "Cleanser"; public void append(String a) { s += a; } public void dilute() { append(" dilute()"); } public void apply() { append(" apply()"); } public void scrub() { append(" scrub()"); } @Override public String toString() { return s; } public static void main(String[] args) { Cleanser x = new Cleanser(); x.dilute(); x.apply(); x.scrub(); System.out.println(x); } } public class Detergent extends Cleanser { // Change a method: @Override public void scrub() { append(" Detergent.scrub()"); super.scrub(); // Call base-class version } // Add methods to the interface: public void foam() { append(" foam()"); } // Test the new class: public static void main(String[] args) { Detergent x = new Detergent(); x.dilute(); x.apply(); x.scrub(); x.foam(); System.out.println(x); System.out.println("Testing base class:"); Cleanser.main(args); } } /* Output: Cleanser dilute() apply() Detergent.scrub() scrub() foam() Testing base class: Cleanser dilute() apply() scrub() */
继承的类初始化就是 base类先初始化。方法调用的话就是派生类第一优先级。
带参数的构造器
// reuse/Chess.java // (c)2017 MindView LLC: see Copyright.txt // We make no guarantees that this code is fit for any purpose. // Visit http://OnJava8.com for more book information. // Inheritance, constructors and arguments class Game { Game(int i) { System.out.println("Game constructor"); } } class BoardGame extends Game { BoardGame(int i) { super(i); System.out.println("BoardGame constructor"); } } public class Chess extends BoardGame { Chess() { super(11); System.out.println("Chess constructor"); } public static void main(String[] args) { Chess x = new Chess(); } } /* Output: Game constructor BoardGame constructor Chess constructor */
因为继承的类都有构造器,所以我们需要重写构造器,当我们写一个构造器。为什么我们写一个带参构造函数,却要super,因为派生类的基类也是带参的,初始化顺序又是基类先开始,如果我们不super给基类一个值,岂不是基类就没法初始化了吗。问题事基类不是先初始化,我们怎么先派生类的构造再传值,这是个问题,不过你可以理解为你如果想吃美食,您得先给厨师一个菜谱,因为厨师是美食的基类。当然也有无参的,你比如说学生和老师的知识(大概),我们不需要给老师一个知识单,他就会先备课初始化。。。
委托,真的有人这么用吗?
// reuse/SpaceShipDelegation.java // (c)2017 MindView LLC: see Copyright.txt // We make no guarantees that this code is fit for any purpose. // Visit http://OnJava8.com for more book information. 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); } }
这样的好处就是不用将基类的所有方法都暴露在派生类中。
组合和继承结合
// reuse/PlaceSetting.java // (c)2017 MindView LLC: see Copyright.txt // We make no guarantees that this code is fit for any purpose. // Visit http://OnJava8.com for more book information. // Combining composition & inheritance class Plate { Plate(int i) { System.out.println("Plate constructor"); } } class DinnerPlate extends Plate { DinnerPlate(int i) { super(i); System.out.println("DinnerPlate constructor"); } } class Utensil { Utensil(int i) { System.out.println("Utensil constructor"); } } class Spoon extends Utensil { Spoon(int i) { super(i); System.out.println("Spoon constructor"); } } class Fork extends Utensil { Fork(int i) { super(i); System.out.println("Fork constructor"); } } class Knife extends Utensil { Knife(int i) { super(i); System.out.println("Knife constructor"); } } // A cultural way of doing something: class Custom { Custom(int i) { System.out.println("Custom constructor"); } } public class PlaceSetting extends Custom { private Spoon sp; private Fork frk; private Knife kn; private DinnerPlate pl; public PlaceSetting(int i) { super(i + 1); sp = new Spoon(i + 2); frk = new Fork(i + 3); kn = new Knife(i + 4); pl = new DinnerPlate(i + 5); System.out.println("PlaceSetting constructor"); } public static void main(String[] args) { PlaceSetting x = new PlaceSetting(9); } } /* Output: Custom constructor Utensil constructor Spoon constructor Utensil constructor Fork constructor Utensil constructor Knife constructor Plate constructor DinnerPlate constructor PlaceSetting constructor */
// reuse/CADSystem.java // (c)2017 MindView LLC: see Copyright.txt // We make no guarantees that this code is fit for any purpose. // Visit http://OnJava8.com for more book information. // Ensuring proper cleanup // {java reuse.CADSystem} package reuse; class Shape { Shape(int i) { System.out.println("Shape constructor"); } void dispose() { System.out.println("Shape dispose"); } } class Circle extends Shape { Circle(int i) { super(i); System.out.println("Drawing Circle"); } @Override void dispose() { System.out.println("Erasing Circle"); super.dispose(); } } class Triangle extends Shape { Triangle(int i) { super(i); System.out.println("Drawing Triangle"); } @Override void dispose() { System.out.println("Erasing Triangle"); super.dispose(); } } class Line extends Shape { private int start, end; Line(int start, int end) { super(start); this.start = start; this.end = end; System.out.println( "Drawing Line: " + start + ", " + end); } @Override void dispose() { System.out.println( "Erasing Line: " + start + ", " + end); super.dispose(); } } public class CADSystem extends Shape { private Circle c; private Triangle t; private Line[] lines = new Line[3]; public CADSystem(int i) { super(i + 1); for(int j = 0; j < lines.length; j++) lines[j] = new Line(j, j*j); c = new Circle(1); t = new Triangle(1); System.out.println("Combined constructor"); } @Override public void dispose() { System.out.println("CADSystem.dispose()"); // The order of cleanup is the reverse // of the order of initialization: t.dispose(); c.dispose(); for(int i = lines.length - 1; i >= 0; i--) lines[i].dispose(); super.dispose(); } public static void main(String[] args) { CADSystem x = new CADSystem(47); try { // Code and exception handling... } finally { x.dispose(); } } } /* Output: Shape constructor Shape constructor Drawing Line: 0, 0 Shape constructor Drawing Line: 1, 1 Shape constructor Drawing Line: 2, 4 Shape constructor Drawing Circle Shape constructor Drawing Triangle Combined constructor CADSystem.dispose() Erasing Triangle Shape dispose Erasing Circle Shape dispose Erasing Line: 2, 4 Shape dispose Erasing Line: 1, 1 Shape dispose Erasing Line: 0, 0 Shape dispose Shape dispose */
这里有个小地方注意就是在new Line[]的时候,并没有创建对象,因为并没有对数组进行初始化,所以还是先走构造。这边一开始也被误解了。这里也还有个GUI的注意地方,我们用语言将进行计算机图形操作的时候,需要在使用完后dispose,典型的一个例子就是Graphics2D。
重写构造
// reuse/Hide.java // (c)2017 MindView LLC: see Copyright.txt // We make no guarantees that this code is fit for any purpose. // Visit http://OnJava8.com for more book information. // Overloading a base-class method name in a derived // class does not hide the base-class versions class Homer { char doh(char c) { System.out.println("doh(char)"); return 'd'; } float doh(float f) { System.out.println("doh(float)"); return 1.0f; } } class Milhouse {} class Bart extends Homer { void doh(Milhouse m) { System.out.println("doh(Milhouse)"); } } public class Hide { public static void main(String[] args) { Bart b = new Bart(); b.doh(1); b.doh('x'); b.doh(1.0f); b.doh(new Milhouse()); } } /* Output: doh(float) doh(char) doh(float) doh(Milhouse) */
这里是构造器都是无参的,所以只是个最普通的继承。
组合和继承的选择,我个人比较喜欢组合,当你想在新类中包含一个已有类的功能时,使用组合,而非继承。也就是说,在新类中嵌入一个对象(通常是私有的),以实现其功能。新类的使用者看到的是你所定义的新类的接口,而非嵌入对象的接口。
老生常谈的Car例子
// reuse/Car.java // Composition with public objects class Engine { public void start() {} public void rev() {} public void stop() {} } class Wheel { public void inflate(int psi) {} } class Window { public void rollup() {} pubilc void rolldown() {} } class Door { public Window window = new Window(); public void open() {} public void close() {} } public class Car { public Engine engine = new Engine(); public Wheel[] wheel = new Wheel[4]; public Door left = new Door(), right = new Door(); // 2-door public Car() { for (int i = 0; i < 4; i++) { wheel[i] = new Wheel(); } } public static void main(String[] args) { Car car = new Car(); car.left.window.rollup(); car.wheel[0].inflate(72); } }
说到继承就不得不谈向上转型
B extends A
A a = new B();
为什么呢,你说一辆凯迪拉克B算不算一辆汽车A呢,同理奥迪C...这是纯面向对象的理解。
final的用法,常量,参数,方法,比较简单举几个不常见的例子
空白final
// reuse/BlankFinal.java // "Blank" final fields 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 constructor public BlankFinal() { j = 1; // Initialize blank final p = new Poppet(1); // Init blank final reference } public BlankFinal(int x) { j = x; // Initialize blank final p = new Poppet(x); // Init blank final reference } public static void main(String[] args) { new BlankFinal(); new BlankFinal(47); } }
虽然第一时间没有初始化final变量,但是必须要在运行时初始化,否则就出问题了。
final方法虽然有个内嵌调用,但是现代java是禁止这么用的,一般来讲我们只对明确不让派生类重写的方法上final锁
一个容易误解的初始化
// reuse/Beetle.java // The full process of initialization class Insect { private int i = 9; protected int j; Insect() { System.out.println("i = " + i + ", j = " + j); j = 39; } private static int x1 = printInit("static Insect.x1 initialized"); static int printInit(String s) { System.out.println(s); return 47; } } public class Beetle extends Insect { private int k = printInit("Beetle.k.initialized"); public Beetle() { System.out.println("k = " + k); System.out.println("j = " + j); } private static int x2 = printInit("static Beetle.x2 initialized"); public static void main(String[] args) { System.out.println("Beetle constructor"); Beetle b = new Beetle(); } }
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39
为什么不是先走sout?因为需要先加载,加载的目的主要事static这类的要提前处理,要不怎么不创建就调用static类的数据?这个我一开始也陷入了传统的顺序结构的误区了。