複用類二
(三)代理
代理:將一個成員物件置於所要構造的類中(就像組合),同時,在新類中暴露了該成員物件的所有方法(就像繼承)。是繼承和組合之間的中庸之道,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