1. 程式人生 > >複用類二

複用類二

(三)代理

代理:將一個成員物件置於所要構造的類中(就像組合),同時,在新類中暴露了該成員物件的所有方法(就像繼承)。是繼承和組合之間的中庸之道,java並沒有提供對它的直接支援。p132

(四)結合使用組合和繼承

一 在將成員物件初始化這一點上要注意

如下:

class Plate {
	Plate(int i) {
		System.out.println("PLATE");
	}
}

class DinnerPlate extends Plate {
	DinnerPlate(int i) {
		super(i);
		System.out.println("DinnerPlate");
	}
}

class Utensil {
	Utensil(int i) {
		System.out.println("Utensil");
	}
}

class Spoon extends Utensil {
	Spoon(int i) {
		super(i);
		System.out.println("SPOON");
	}
}

class Fork extends Utensil {
	Fork(int i) {
		super(i);
		System.out.println("Fork");
	}
}

class Knife extends Utensil {
	Knife(int i) {
		super(i);
		System.out.println("Knife");
	}
}

class Custom {
	Custom(int i) {
		System.out.println("Custom");
	}
}

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");
	}

	public static void main(String[] args) {
		PlaceSetting x = new PlaceSetting(9);
	}
}

結果為:

Custom
Utensil
SPOON
Utensil
Fork
Utensil
Knife
PLATE
DinnerPlate
PlaceSetting

二 確保正確清理

如果想要某個類清理一些東西,就必須顯示的編寫一個特殊的方法來做這件事,並確保客戶端程式設計師知曉他們必須呼叫這一方法,因此,首要任務是:將這一清理動作置於finally子句中,以預防異常的出現。
如下:
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(“Circle”);
}

void dispose() {
	System.out.println("Erasing Circle");
	super.dispose();
}

}

class Triangle extends Shape {
Triangle(int i) {
super(i);
System.out.println(“Triangle”);
}

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");
}

void dispose() {
	System.out.println("Erasing Line");
	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("CADSystem constructed");
}

public void dispose() {
	System.out.println("CADSystem dispose");
	// 清除順序和初始化的順序是相反的
	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 {
		// 程式碼和異常處理
	} finally {
		x.dispose();

	}
}}

結果為:

Shape Constructor
Shape Constructor
Drawing Line
Shape Constructor
Drawing Line
Shape Constructor
Drawing Line
Shape Constructor
Circle
Shape Constructor
Triangle
CADSystem constructed

(1)以上程式中,每個類都有自己的dispose()方法將未存於記憶體之中的東西恢復到物件存在之前的狀態。
(2)dispose()中要注意對基類清理方法和成員物件清理方法的呼叫順序,以防止某個子物件以來於另一個子物件情形的發生:首先,執行類的所有特定的清理工作,其順序同生成順序相反(通常要求基類元素仍然存活),然後,呼叫基類的清理方法。
(3)親自處理清理時,最好的辦法是除了記憶體以外,不能依賴垃圾回收器,如果要清理,最好是編寫自己的清理方法,但是不要使用finalize()。

三 名稱遮蔽

(1)使用與基類完全相同的特徵簽名(方法的名稱和引數型別)和返回值型別來覆蓋(重寫)具有相同名稱的方法。
(2)JavaSE5新增加了@Override註解,當想要覆寫某個方法時,可以新增這個註解。防止意外的過載。

class Homer {
	char doh(char c) {
		System.out.println("doh(char)");
		return 'd';
	}

	float doh(float f) {
		System.out.println("doh(float)");
		return 1.09f;
	}
}

class Milhouse {
}

class Bart extends Homer {
	void doh(Milhouse m) {
		System.out.println("don()");
	}
}

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());
	}
}

結果如下:

doh(float)
doh(char)
doh(float)
don()

(五) 在繼承與組合之間選擇

(1)組合和繼承都允許在新類中放置子類物件,組合是顯示的這樣做,而繼承是隱式的做。
(2)一般情況下,應使域成為private。

class Engine {
	public void start() {
	}

	public void rev() {
	}

	public void stop() {
	}
}

class Wheel {
	public void inflate(int psi) {
	}

}

class Window {
	public void rollup() {
	}

	public void rolldown() {
	}
}

class Door {
	public Window w = new Window();

	public void open() {
	}

	public void close() {
	}
}

public class Car {
	public Engine engine = new Engine();
	public Wheel[] wheels = new Wheel[4];
	public Door left = new Door();
	public Door right = new Door();

	public Car() {
		for (int i = 0; i < 4; i++) {
			wheels[i] = new Wheel();
		}
	}

	public static void main(String[] args) {
		Car car = new Car();
		car.left.w.rollup();
		car.wheels[0].inflate(72);
	}

}

( 六) protected關鍵字

(1)對於繼承於此類的匯出類或者任何位於同一個包內的類來說,可以訪問。(protected提供了包訪問許可權)
(2)最好的方式還是使域繼續保持為private,以便保留“更改底層實現”的權利,然後通過protected方法控制類的繼承者的訪問許可權。

class Villain {
	private String name;

	protected void set(String nm) {
		name = nm;
	}

	public Villain(String name) {
		this.name = name;
	}

	public String toString() {
		return "I'm a Villlain and my name is" + name;
	}

}

public class Orc extends Villain {
	private int orcNumber;

	public Orc(String name, int orcNumber) {
		super(name);
		this.orcNumber = orcNumber;
	}

	public void change(String name, int orcNumber) {
		set(name); // 可變的。因為是protected。
		this.orcNumber = orcNumber;
	}

	public String toString() {
		return "Orc" + orcNumber + ":" + super.toString();
	}

	public static void main(String[] args) {
		Orc orc = new Orc("Linburger", 12);
		System.out.println(orc);
		orc.change("Bob", 19);
		System.out.println(orc);
	}
}

結果為:

Orc12:I'm a Villlain and my name isLinburger
Orc19:I'm a Villlain and my name isBob

(七) 向上轉型

(1)關係:新類是現有類的一種型別

class Instrument {
	public void play() {
		System.out.println("Instrucment");
	}

	static void tune(Instrument i) {// 程式碼可以對Instrument 和它的所有匯出類起作用
		i.play();
	}

}

public class Wind extends Instrument {
	/*
	 * public void play() { System.out.println("play"); }
	 */
	public static void main(String[] args) {
		Wind flute = new Wind();
		Instrument.tune(flute);// 向上轉型(將Wind引用轉化成Instrument引用)
	}
}

結果為:

Instrucment

(2)Wind物件也是一種Instrucment物件,不存在任何tune()方法是可以通過Instrucment來呼叫,同時不存在於Wind中的。
(3)將Wind轉換成Instrucment引用的動作,稱為向上轉型。

一 為什麼稱為向上轉型

(1)傳統繼承圖的繪製方法
注意:在向上轉型中,類介面唯一可能發生的事是丟失方法,而不是獲取它們。

二 再論組合與繼承

要用組合還是繼承:若向上轉型,繼承時必要的,否則應該仔細考慮慎用。

(八)final關鍵字

final:不想改變的。
原因:設計(方法鎖定,確保繼承時不會被覆蓋,方法行為不變)和效率(將final修飾的方法所有的呼叫轉為內嵌呼叫,以節省開銷)

final資料

許多程式語言都有某種方法,向編譯器告知一塊資料是恆定不變的,恆定不變有時很有用,如:
(1)一個永不改變的編譯時常量。
(2)一個在執行時被初始化的值,而你不希望它被改變

  • 可以在編譯時將常量帶入到任意一個可能用到它的表示式,減輕了執行時的負擔,在java中這類常量必須是:基本型別的+final表示+定義時賦值。
  • static+final:只佔據一段不能改變的儲存空間,通常大寫,下劃線分隔單詞。
  • final使引用不變,即無法使它改為指向另一個物件,但物件自身可以被修改。java並未提供使任何物件恆定不變的途徑。
  • 不能因為某資料是final的就認為在編譯時可以知道它的值,例如:執行時使用隨機數生成數值來初始化某變數。
import java.util.Random;

class Value {
	int i;

	public Value(int i) {
		this.i = i;
	}
}

public class FinalData {
	private static Random rand = new Random(47);
	private String id;

	public FinalData(String id) {
		this.id = id;
	}

	// Can be compile-time constants
	private final int valueOne = 99;
	private static final int VALUE_TWO = 99;
	// Typical public constant
	public static final int VALUE_THREE = 39;
	// Cannot be compile-time constants
	private final int i4 = rand.nextInt(30);
	static final int INT_5 = rand.nextInt(20);
	private Value v1 = new Value(11);
	private final Value v2 = new Value(22);
	private static final Value v3 = new Value(33);
	// Arrays
	private final int[] a = { 1, 2, 3, 4, 5 };

	public String toString() {
		return "id=" + id + " " + "INT-5=" + INT_5 + " " + "i4=" + i4;
	}

	public static void main(String[] args) {
		FinalData fd1 = new FinalData("fd1");
		// fd1.valueOne++; The final field FinalData.valueOne cannot be assigned
		fd1.v2.i++; // Object isn't constant!
		fd1.v1 = new Value(9); // Ok--not final
		// fd1.v2=new Value(5); The final field FinalData.v2 cannot be assigned
		for (int i = 0; i < fd1.a.length; i++)
			fd1.a[i]++;// //Object isn't constant!
		// fd1.a=new int[3];
		System.out.println(fd1);
		FinalData fd2 = new FinalData("fd2");
		System.out.println(fd1);
		System.out.println(fd2);
     }
}

結果為:

id=fd1 INT-5=18 i4=5
id=fd1 INT-5=18 i4=5
id=fd2 INT-5=18 i4=13

注意:INT-5是static的,在裝載時已經初始化,而不是每次建立新物件時都初始化。

空白final

(1)空白final:宣告為final但又未給定初值的域。
(2)無論什麼情況,編譯器都能確保空白final在使用前初始化。
(3)用途:提供了更大的靈活性。一個類中的final域就可以做到根據物件而有所不同,卻又保持其恆定不變的特性。
(4)在域的定義處或者每個構造器中對其賦值,以保證總會被初始化。

class Poppet {
	private int i;

	Poppet(int ii) {
		i = ii;
	}
}

public class BlankFinal {
	private final int i = 0;// Initialised final
	private final int j;// Blank final
	private final Poppet p;// Blank final reference;
	// Blank finals MUST be initializes in the constructor

	public BlankFinal() {
		j = 1;
		p = new Poppet(1);
		// j=8;
	}

	public BlankFinal(int x) {
		j = x;
		p = new Poppet(x);
	}

	public String toString() {
		return "j=" + j + " " + "p=" + p;
	}

	public static void main(String[] args) {
		System.out.println(new BlankFinal());
		System.out.println(new BlankFinal(47));
	}
}

結果為:

j=1 [email protected]
j=47 [email protected]

final引數

(1)引數列表中以宣告的方式將引數指定為final。這意味著:無法在方法中更改引數引用所指向的物件。
(2)僅可以讀引數,無法改變。這一特性主要用於向匿名內部類傳遞引數。

**
 * final引數(無法在方法中更改引數引用所指向的物件)
 * 
 * @author 12345678 2019年1月2日
 */
class Gizmo {
	public void spin() {
		System.out.println("spin()");
	}
}

public class FinalArguments {
	void with(final Gizmo g) {
		// g=new Gizmo(); The final local variable g cannot be assigned.
		System.out.println("with"); // It must be blank and not using a compound assignment
	}

	void without(Gizmo g) {
		g = new Gizmo();
		g.spin();
	}

	// void f(final int i) {i++;} Can't change
	// You can only read from a final primitive
	int g(final int i) {
		return i + 1;
	}

	public static void main(String[] args) {
		FinalArguments bf = new FinalArguments();
		bf.without(null);
		bf.with(null);
		System.out.println(bf.g(9));
	}

}

結果為:

spin()
with
10

注意:類中所有的private方法都是隱式的指定為final的。

final類

原因:
(1)設計:不做任何改動
(2)安全:不希望有子類
class SmallBrain {
}

final class Dinosaur {
	int i = 7;
	int j = 1;
	SmallBrain x = new SmallBrain();

	void f() {
	}
}
//class Further extends Dinosaur{}
public class Jurassic {
	public static void main(String[] args) {
		Dinosaur n = new Dinosaur();
		n.f();
		n.i = 40;
		n.j++;
		System.out.println(n.i);
	}
}

結果為:

40

有關final的忠告

p145

final與static相關連結