在《Think in Java》中有這樣一句話:復(fù)用代碼是 Java 眾多引人注目的功能之一。但要想成為極具革命性的語言,僅僅能夠復(fù)制代碼并對(duì)加以改變是不夠的,它還必須能夠做更多的事情。在這句話中最引人注目的是“復(fù)用代碼”,盡可能的復(fù)用代碼使我們程序員一直在追求的,現(xiàn)在我來介紹一種復(fù)用代碼的方式,也是 Java 三大特性之一—繼承。
在講解之前我們先看一個(gè)例子,該例子是前篇博文( Java提高篇—–理解java 的三大特性之封裝)的。
從這里我們可以看出, Wife、Husband 兩個(gè)類除了各自的 husband、wife 外其余部分全部相同,作為一個(gè)想最大限度實(shí)現(xiàn)復(fù)用代碼的我們是不能夠忍受這樣的重復(fù)代碼,如果再來一個(gè)小三、小四、小五……(扯遠(yuǎn)了大笑)我們是不是也要這樣寫呢?那么我們?nèi)绾蝸韺?shí)現(xiàn)這些類的可復(fù)用呢?利用繼承!!
首先我們先離開軟件編程的世界,從常識(shí)中我們知道丈夫、妻子、小三、小四…,他們都是人,而且都有一些共性,有名字、年齡、性別、頭等等,而且他們都能夠吃東西、走路、說話等等共同的行為,所以從這里我們可以發(fā)現(xiàn)他們都擁有人的屬性和行為,同時(shí)也是從人那里繼承來的這些屬性和行為的。
從上面我們就可以基本了解了繼承的概念了,繼承是使用已存在的類的定義作為基礎(chǔ)建立新類的技術(shù),新類的定義可以增加新的數(shù)據(jù)或新的功能,也可以用父類的功能,但不能選擇性地繼承父類。通過使用繼承我們能夠非常方便地復(fù)用以前的代碼,能夠大大的提高開發(fā)的效率。
對(duì)于 Wife、Husband 使用繼承后,除了代碼量的減少我們還能夠非常明顯的看到他們的關(guān)系。
繼承所描述的是“is-a”的關(guān)系,如果有兩個(gè)對(duì)象A和B,若可以描述為“A是B”,則可以表示 A 繼承 B,其中 B 是被繼承者稱之為父類或者超類,A 是繼承者稱之為子類或者派生類。
實(shí)際上繼承者是被繼承者的特殊化,它除了擁有被繼承者的特性外,還擁有自己獨(dú)有得特性。例如貓有抓老鼠、爬樹等其他動(dòng)物沒有的特性。同時(shí)在繼承關(guān)系中,繼承者完全可以替換被繼承者,反之則不可以,例如我們可以說貓是動(dòng)物,但不能說動(dòng)物是貓就是這個(gè)道理,其實(shí)對(duì)于這個(gè)我們將其稱之為“向上轉(zhuǎn)型”,下面介紹。
誠然,繼承定義了類如何相互關(guān)聯(lián),共享特性。對(duì)于若干個(gè)相同或者相識(shí)的類,我們可以抽象出他們共有的行為或者屬相并將其定義成一個(gè)父類或者超類,然后用這些類繼承該父類,他們不僅可以擁有父類的屬性、方法還可以定義自己獨(dú)有的屬性或者方法。
同時(shí)在使用繼承時(shí)需要記住三句話:
1、子類擁有父類非 private 的屬性和方法。
2、子類可以擁有自己屬性和方法,即子類可以對(duì)父類進(jìn)行擴(kuò)展。
3、子類可以用自己的方式實(shí)現(xiàn)父類的方法。(以后介紹)。
綜上所述,使用繼承確實(shí)有許多的優(yōu)點(diǎn),除了將所有子類的共同屬性放入父類,實(shí)現(xiàn)代碼共享,避免重復(fù)外,還可以使得修改擴(kuò)展繼承而來的實(shí)現(xiàn)比較簡(jiǎn)單。
誠然,講到繼承一定少不了這三個(gè)東西:構(gòu)造器、protected 關(guān)鍵字、向上轉(zhuǎn)型。
通過前面我們知道子類可以繼承父類的屬性和方法,除了那些 private 的外還有一樣是子類繼承不了的—構(gòu)造器。對(duì)于構(gòu)造器而言,它只能夠被調(diào)用,而不能被繼承。 調(diào)用父類的構(gòu)造方法我們使用 super() 即可。
對(duì)于子類而已,其構(gòu)造器的正確初始化是非常重要的,而且當(dāng)且僅當(dāng)只有一個(gè)方法可以保證這點(diǎn):在構(gòu)造器中調(diào)用父類構(gòu)造器來完成初始化,而父類構(gòu)造器具有執(zhí)行父類初始化所需要的所有知識(shí)和能力。
public class Person {
protected String name;
protected int age;
protected String sex;
Person(String name){
System.out.println("Person Constrctor-----" + name);
}
}
public class Husband extends Person{
private Wife wife;
Husband(){
super("chenssy");
System.out.println("Husband Constructor...");
}
public static void main(String[] args) {
Husband husband = new Husband();
}
}
Output:
Person Constrctor-----chenssy
Husband Constructor...
所以綜上所述:對(duì)于繼承而已,子類會(huì)默認(rèn)調(diào)用父類的構(gòu)造器,但是如果沒有默認(rèn)的父類構(gòu)造器,子類必須要顯示的指定父類的構(gòu)造器,而且必須是在子類構(gòu)造器中做的第一件事(第一行代碼)。
private 訪問修飾符,對(duì)于封裝而言,是最好的選擇,但這個(gè)只是基于理想的世界,有時(shí)候我們需要這樣的需求:我們需要將某些事物盡可能地對(duì)這個(gè)世界隱藏,但是仍然允許子類的成員來訪問它們。這個(gè)時(shí)候就需要使用到 protected。
對(duì)于 protected 而言,它指明就類用戶而言,他是 private,但是對(duì)于任何繼承與此類的子類而言或者其他任何位于同一個(gè)包的類而言,他卻是可以訪問的。
public class Person {
private String name;
private int age;
private String sex;
protected String getName() {
return name;
}
protected void setName(String name) {
this.name = name;
}
public String toString(){
return "this name is " + name;
}
/** 省略其他setter、getter方法 **/
}
public class Husband extends Person{
private Wife wife;
public String toString(){
setName("chenssy"); //調(diào)用父類的setName();
return super.toString(); //調(diào)用父類的toString()方法
}
public static void main(String[] args) {
Husband husband = new Husband();
System.out.println(husband.toString());
}
}
Output:
this name is chenssy
從上面示例可以看書子類 Husband 可以明顯地調(diào)用父類 Person 的 setName()。
誠然盡管可以使用 protected 訪問修飾符來限制父類屬性和方法的訪問權(quán)限,但是最好的方式還是將屬性保持為 private (我們應(yīng)當(dāng)一致保留更改底層實(shí)現(xiàn)),通過 protected 方法來控制類的繼承者的訪問權(quán)限。
在上面的繼承中我們談到繼承是 is-a 的相互關(guān)系,貓繼承與動(dòng)物,所以我們可以說貓是動(dòng)物,或者說貓是動(dòng)物的一種。這樣將貓看做動(dòng)物就是向上轉(zhuǎn)型。如下:
public class Person {
public void display(){
System.out.println("Play Person...");
}
static void display(Person person){
person.display();
}
}
public class Husband extends Person{
public static void main(String[] args) {
Husband husband = new Husband();
Person.display(husband); //向上轉(zhuǎn)型
}
}
在這我們通過 Person.display(husband)。這句話可以看出 husband 是 person 類型。
將子類轉(zhuǎn)換成父類,在繼承關(guān)系上面是向上移動(dòng)的,所以一般稱之為向上轉(zhuǎn)型。由于向上轉(zhuǎn)型是從一個(gè)叫專用類型向較通用類型轉(zhuǎn)換,所以它總是安全的,唯一發(fā)生變化的可能就是屬性和方法的丟失。這就是為什么編譯器在“未曾明確表示轉(zhuǎn)型”活“未曾指定特殊標(biāo)記”的情況下,仍然允許向上轉(zhuǎn)型的原因。
上面講了繼承所帶來的諸多好處,那我們是不是就可以大肆地使用繼承呢?送你一句話:慎用繼承。
首先我們需要明確,繼承存在如下缺陷:
1、父類變,子類就必須變。
2、繼承破壞了封裝,對(duì)于父類而言,它的實(shí)現(xiàn)細(xì)節(jié)對(duì)與子類來說都是透明的。
3、繼承是一種強(qiáng)耦合關(guān)系。
所以說當(dāng)我們使用繼承的時(shí)候,我們需要確信使用繼承確實(shí)是有效可行的辦法。那么到底要不要使用繼承呢?《Think in Java》中提供了解決辦法:?jiǎn)栆粏栕约菏欠裥枰獜淖宇愊蚋割愡M(jìn)行向上轉(zhuǎn)型。如果必須向上轉(zhuǎn)型,則繼承是必要的,但是如果不需要,則應(yīng)當(dāng)好好考慮自己是否需要繼承。
慎用繼承?。。。。。。。。。。。。。。。。。。。。。。。。。?!