繼承和組合的使用時機
到底是該用組合還是繼承捞镰,一個最清晰的辦法就是判斷是否需要從新類向基類進行向上轉(zhuǎn)型闸与。如果必須向上轉(zhuǎn)型,則繼承是必要的岸售。
什么時候践樱,一定會用到向上轉(zhuǎn)型呢?當(dāng)然是子類在定義時需要調(diào)用父類方法的時候凸丸。
另外拷邢,子類與父類的關(guān)系是 is-a 的關(guān)系。
final 數(shù)據(jù)
final 修飾常量時屎慢,表示這個常量是不改變的瞭稼。一個既是 static 又是 final 的域只占據(jù)一段不能改變的存儲空間。
final 修飾引用時腻惠,表示 final 使得引用恒定不變环肘,一旦引用被初始化指向一個對象,就無法再把它改為指向另外一個對象集灌。
然而悔雹,對象其自身卻是可以被修改的。這一限制同樣適用于數(shù)組,它也是對象腌零。
public class A{
final int[] a0 = {1};
}
class TestA{
void test(){
A a = new A();
a.a0[0]++;
}
}
空白 final
Java 允許在聲明變量但未賦值的情況下用final修飾梯找,但是 final 修飾的變量必須在 constructor 中被賦值。
這樣的做法益涧,保證了靈活性初肉。
class Poppet{
private int i;
Poppet(int x){
i = x;
}
}
public class BlankFinal{
private final int j;
private final Poppet poppet;
public BlankFinal(){
j = 0;
poppet = new Poppet(j);
}
public BlankFinal(int x){
j = x;
poppet = new Poppet(x);
}
}
final 修飾參數(shù)
final 也可以用來修飾參數(shù),表示參數(shù)不可以被更改饰躲。這一特性主要是用來向匿名內(nèi)部類中傳遞數(shù)據(jù)牙咏。
class Gizmo{
public void spin(){}
}
public class FinalArguments{
void with(final Gizmo g){
// g = new Gizmo() 這種寫法是錯誤的,因為 g 是 final,這樣的寫法保證了 g 只具有只讀屬性嘹裂。
}
}
final 方法
使用 final 方法有兩個原因妄壶,第一個原因是把方法鎖定,以防止任何繼承類修改它的含義寄狼。
這是出于設(shè)計的考慮丁寄,想要確保繼承在子類中方法不變,并且不會被覆蓋泊愧。
第二個原因是伊磺,早期發(fā)現(xiàn) final 修飾的方法會更快,現(xiàn)在已經(jīng)不需要了删咱。
final 和 private 關(guān)鍵字
類中所有的 private 方法都隱式指定為 final的屑埋。由于無法取用 private 方法,也就無法覆蓋它痰滋。
final 類
當(dāng)將某個類的整體定義為 final 時摘能,表明該類不允許繼承
7.9 初始化及類的加載
在 Beetle 上運行 Java 時,所發(fā)生的第一件事就是試圖訪問 Beetle.main() (一個 static 方法)敲街,
于是加載器開始啟動并找出 Bettle 類的編譯代碼(在名為 Bettle.class 的文件之中)团搞。在對它進行加載
的過程中,編譯器注意到它有一個基類(由關(guān)鍵字 extends 得知的)多艇,于是它繼續(xù)進行加載逻恐。
對象中所有的基本類型都會被設(shè)為默認(rèn)值,對象引用被設(shè)為 null ——這是由將對象內(nèi)存設(shè)為二進制零值而一舉生成的峻黍。
然后复隆,基類的構(gòu)造器會被調(diào)用。在本例中奸披,它是被自動調(diào)用的昏名。但也可以用 super 來指定對基類構(gòu)造器的調(diào)用。
在開始設(shè)計時阵面,一般優(yōu)先選擇使用組合,只有必要時才選擇繼承。因為組合更具有靈活性样刷。此外仑扑,通過對成員類型使用
繼承技術(shù)的添加技巧,可以在運行時改變那些成員對象的類型和行為置鼻。
多態(tài)
將一個方法調(diào)用同一個主體關(guān)聯(lián)起來被成為綁定镇饮,若程序執(zhí)行前進行綁定,叫做前期綁定箕母。
后期綁定储藐,也叫動態(tài)綁定或運行時綁定,在運行時根據(jù)對象的類型進行綁定嘶是。Java 中除了 static 和 final 方法钙勃,其他所有的方法
都是后期綁定的。為什么要講某個方法聲明為 final 呢聂喇?它可以防止其他人覆蓋該方法辖源,可以有效地關(guān)閉動態(tài)綁定,告訴編譯器不需要對其進行動態(tài)綁定希太。
多態(tài)的缺陷
- 私有方法無法重載
public class PrivateOverride {
private void f(){
System.out.println("private f()");
}
public static void main(String[] args){
PrivateOverride po = new Derived();
po.f();
}
}
class Derived extends PrivateOverride {
public void f(){
System.out.println("public f()");
}
}
- 只有普通的方法調(diào)用可以是多態(tài)的
只有普通的方法調(diào)用可以是多態(tài)的克饶,如果直接訪問某個屬性,那么這個訪問將在編譯期間就進行解析
class Super{
public int filed = 0;
public int getFiled(){
return filed;
}
}
class Sub extends Super{
public int filed = 1;
public int getFiled(){
return field;
}
public int getSuperField(){
return super.field;
}
}
public class FieldAccess{
public static void main(String[] args){
Super sup = new Sub();
// 對屬性的訪問是在編譯期間就確定的
System.out.println("sup.field = " + sup.field +
", sup.getField() = " + sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = " +
sub.field + ", sub.getFiled() = " +
sub.getField() +
sub.getSuperField());
}
}
8.3 構(gòu)造器和多態(tài)
- 調(diào)用基類構(gòu)造器誊辉,這個步驟會反復(fù)地不斷遞歸下去矾湃。首先是構(gòu)造這種層次的根,然后是下一層導(dǎo)出類堕澄,直到最低層的導(dǎo)出類洲尊。
- 按照聲明順序調(diào)用成員的初始化方法
- 調(diào)用導(dǎo)出類構(gòu)造器的主體
package com.zzjack.wxorder.javathought;
class Meal{
Meal(){
System.out.println("Meal()");
}
}
class Bread{
Bread(){
System.out.println("Bread()");
}
}
class Cheese{
Cheese(){
System.out.println("Cheese()");
}
}
class Lettuce{
Lettuce(){
System.out.println("Lettuce()");
}
}
class Lunch extends Meal{
Lunch(){
System.out.println("Lunch()");
}
}
class PortableLunch extends Lunch{
PortableLunch(){
System.out.println("PortableLunch");
}
}
public class Sandwich extends PortableLunch{
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
public Sandwich(){
System.out.println("Sanwich()");
}
public static void main(String[] args){
new Sandwich();
}
}
8.3.3 構(gòu)造器內(nèi)部的多態(tài)方法的行為
在其他任何事物發(fā)生之前,將分配給對象的存儲空間初始化成二進制的零奈偏。
如前所述的那樣調(diào)用基類構(gòu)造器坞嘀。此時,調(diào)用被覆蓋后的 draw() 方法(要在調(diào)用 RoundGlyph 構(gòu)造器之前調(diào)用)惊来,
由于步驟1的緣故丽涩,我們此時會發(fā)現(xiàn) radius 的值為 0按照聲明的順序調(diào)用成員的初始化方法
調(diào)用導(dǎo)出類的構(gòu)造器主體
編寫構(gòu)造器時有一條有效的準(zhǔn)則:用盡可能簡單的方法使對象進入正常狀態(tài),如果可以的話裁蚁,避免調(diào)用其它方法矢渊。在構(gòu)造器內(nèi)
唯一能夠安全調(diào)用的那些方法是基類中的 final 方法。final 方法不能被覆蓋枉证,因此也就不會出現(xiàn)上述令人驚訝的問題矮男。
8.4 協(xié)變返回類型
在設(shè)計時,組合優(yōu)先于繼承室谚。因為組合更加靈活毡鉴,它可以動態(tài)選擇類型崔泵。
class Actor{
public void act(){};
}
class HappyActor extends Actor{
public void act() {
System.out.println("HappyActor");
}
}
class SadActor extends Actor{
public void act(){
System.out.println("SadActor");
}
}
class Stage{
private Actor actor = new HappyActor();
public void change(){
actor = new SadActor();
}
public void performPlay(){
actor.act();
}
}
public class Transmogrify {
public static void main(String[] args){
Stage stage=new Stage();
stage.performPlay();
stage.change();
stage.performPlay();
}
}
在 Stage 中,performPlay() 的的輸出結(jié)果會隨著 change() 而改變猪瞬,
這樣一來憎瘸,我們在運行期間就獲得了動態(tài)靈活性,這被稱為是狀態(tài)模式陈瘦。
用繼承來表達行為間的差異幌甘,并用字段表達狀態(tài)上的變化。這是一條通用的編程準(zhǔn)則痊项,
這個例子中锅风,通過繼承得到了兩個不同的類,用于表達 act() 方法上的差異鞍泉,而 Stae
運用組合使自己的狀態(tài)發(fā)生變化皱埠。
向上轉(zhuǎn)型和向下轉(zhuǎn)型
向上轉(zhuǎn)型是安全的,因為子類一定具備了父類的接口塞弊。
但是漱逸,向下轉(zhuǎn)型不一定安全。因為子類可以擴展自己獨有的接口游沿。
向下轉(zhuǎn)型需要進行類型轉(zhuǎn)換饰抒,并且是在進入運行期對其進行類型檢查。
class Useful{
public void f() {}
public void g() {}
}
class MoreUseful extends Useful{
public void f() {}
public void g() {}
public void u() {}
public void v() {}
public void w() {}
}
public class RTTI {
public static void main(String[] args){
Useful[] x = {
new Useful(),
new MoreUseful(),
};
x[0].f();
x[1].g();
((MoreUseful) x[1]).w();
((MoreUseful) x[0]).w();
}
}