「萬字圖文」史上最姨母級(jí)Java繼承詳解

課程導(dǎo)學(xué)
在Java課堂中,所有老師不得不提到面向?qū)ο?/strong>(Object Oriented),而在談到面向?qū)ο蟮臅r(shí)候,又不得不提到面向?qū)ο蟮娜筇卣鳎悍庋b、繼承、多態(tài)。三大特征緊密聯(lián)系而又有區(qū)別,本課程就帶你學(xué)習(xí)Java的繼承。
你可能不知道繼承到底有什么用,但你大概率曾有過這樣的經(jīng)歷:寫Java項(xiàng)目/作業(yè)時(shí)候創(chuàng)建很多相似的類,類中也有很多相同的方法,做了很多重復(fù)的工作量,感覺很臃腫。而合理使用繼承就能大大減少重復(fù)代碼,提高代碼復(fù)用性。

繼承的初相識(shí)
學(xué)習(xí)繼承,肯定是先從廣的概念了解繼承是什么以及其作用,然后才從細(xì)的方面學(xué)習(xí)繼承的具體實(shí)現(xiàn)細(xì)節(jié),本關(guān)就是帶你先快速了解和理解繼承的重要概念。
什么是繼承
繼承(英語:inheritance)是面向?qū)ο筌浖夹g(shù)中的一個(gè)概念。它使得復(fù)用以前的代碼非常容易,能夠大大縮短開發(fā)周期,降低開發(fā)費(fèi)用。
Java語言是非常典型的面向?qū)ο蟮恼Z言,在Java語言中繼承就是子類繼承父類的屬性和方法,使得子類對(duì)象(實(shí)例)具有父類的屬性和方法,或子類從父類繼承方法,使得子類具有父類相同的方法。父類有時(shí)也叫基類、超類;子類有時(shí)也被稱為派生類。
我們來舉個(gè)例子:我們知道動(dòng)物有很多種,是一個(gè)比較大的概念。在動(dòng)物的種類中,我們熟悉的有貓(Cat)、狗(Dog)等動(dòng)物,它們都有動(dòng)物的一般特征(比如能夠吃東西,能夠發(fā)出聲音),不過又在細(xì)節(jié)上有區(qū)別(不同動(dòng)物的吃的不同,叫聲不一樣)。在Java語言中實(shí)現(xiàn)Cat和Dog等類的時(shí)候,就需要繼承Animal這個(gè)類。繼承之后Cat、Dog等具體動(dòng)物類就是子類,Animal類就是父類。

為什么需要繼承
你可能會(huì)疑問為什么需要繼承?在具體實(shí)現(xiàn)的時(shí)候,我們創(chuàng)建Dog,Cat等類的時(shí)候?qū)崿F(xiàn)其具體的方法不就可以了嘛,實(shí)現(xiàn)這個(gè)繼承似乎使得這個(gè)類的結(jié)構(gòu)不那么清晰。
如果僅僅只有兩三個(gè)類,每個(gè)類的屬性和方法很有限的情況下確實(shí)沒必要實(shí)現(xiàn)繼承,但事情并非如此,事實(shí)上一個(gè)系統(tǒng)中往往有很多個(gè)類并且有著很多相似之處,比如貓和狗同屬動(dòng)物,或者學(xué)生和老師同屬人。各個(gè)類可能又有很多個(gè)相同的屬性和方法,這樣的話如果每個(gè)類都重新寫不僅代碼顯得很亂,代碼工作量也很大。
這時(shí)繼承的優(yōu)勢(shì)就出來了:可以直接使用父類的屬性和方法,自己也可以有自己新的屬性和方法滿足拓展,父類的方法如果自己有需求更改也可以重寫。這樣使用繼承不僅大大的減少了代碼量,也使得代碼結(jié)構(gòu)更加清晰可見。

所以這樣從代碼的層面上來看我們?cè)O(shè)計(jì)這個(gè)完整的Animal類是這樣的:
class?Animal
{
????public?int?id;
????public?String?name;
????public?int?age;
????public?int?weight;
????public?Animal(int?id,?String?name,?int?age,?int?weight)?{
????????this.id?=?id;
????????this.name?=?name;
????????this.age?=?age;
????????this.weight?=?weight;
????}
????//這里省略get?set方法
????public?void?sayHello()
????{
????????System.out.println("hello");
????}
????public?void?eat()
????{
????????System.out.println("I'm?eating");
????}
????public?void?sing()
????{
????????System.out.println("sing");
????}
}
而Dog,Cat,Chicken類可以這樣設(shè)計(jì):
class?Dog?extends?Animal//繼承animal
{
????public?Dog(int?id,?String?name,?int?age,?int?weight)?{
????????super(id,?name,?age,?weight);//調(diào)用父類構(gòu)造方法
????}
}
class?Cat?extends?Animal{
????public?Cat(int?id,?String?name,?int?age,?int?weight)?{
????????super(id,?name,?age,?weight);//調(diào)用父類構(gòu)造方法
????}
}
class?Chicken?extends?Animal{
????public?Chicken(int?id,?String?name,?int?age,?int?weight)?{
????????super(id,?name,?age,?weight);//調(diào)用父類構(gòu)造方法
????}
????//雞下蛋
????public?void?layEggs()
????{
????????System.out.println("我是老母雞下蛋啦,咯噠咯!咯噠咯!");
????}
}
各自的類繼承Animal后可以直接使用Animal類的屬性和方法而不需要重復(fù)編寫,各個(gè)類如果有自己的方法也可很容易地拓展。上述代碼中你需要注意extends就是用來實(shí)現(xiàn)繼承的。
繼承的分類
繼承分為單繼承和多繼承,Java語言只支持類的單繼承,但可以通過實(shí)現(xiàn)接口的方式達(dá)到多繼承的目的。我們先用一張表概述一下兩者的區(qū)別,然后再展開講解。
| 定義 | 優(yōu)缺點(diǎn) | |
|---|---|---|
單繼承![]() | 一個(gè)子類只擁有一個(gè)父類 | 優(yōu)點(diǎn):在類層次結(jié)構(gòu)上比較清晰 缺點(diǎn):結(jié)構(gòu)的豐富度有時(shí)不能滿足使用需求 |
多繼承(Java不支持,但可以用其它方式滿足多繼承使用需求)![]() | 一個(gè)子類擁有多個(gè)直接的父類 | 優(yōu)點(diǎn):子類的豐富度很高 缺點(diǎn):容易造成混亂 |
單繼承
單繼承,是一個(gè)子類只擁有一個(gè)父類,如我們上面講過的Animal類和它的子類。單繼承在類層次結(jié)構(gòu)上比較清晰,但缺點(diǎn)是結(jié)構(gòu)的豐富度有時(shí)不能滿足使用需求。
多繼承(Java不支持,但可以實(shí)現(xiàn))
多繼承,是一個(gè)子類擁有多個(gè)直接的父類。這樣做的好處是子類擁有所有父類的特征,子類的豐富度很高,但是缺點(diǎn)就是容易造成混亂。下圖為一個(gè)混亂的例子。

Java雖然不支持多繼承,但是Java有三種實(shí)現(xiàn)多繼承效果的方式,分別是內(nèi)部類、多層繼承和實(shí)現(xiàn)接口。
內(nèi)部類可以繼承一個(gè)與外部類無關(guān)的類,保證了內(nèi)部類的獨(dú)立性,正是基于這一點(diǎn),可以達(dá)到多繼承的效果。
多層繼承:子類繼承父類,父類如果還繼承其他的類,那么這就叫多層繼承。這樣子類就會(huì)擁有所有被繼承類的屬性和方法。

實(shí)現(xiàn)接口無疑是滿足多繼承使用需求的最好方式,一個(gè)類可以實(shí)現(xiàn)多個(gè)接口滿足自己在豐富性和復(fù)雜環(huán)境的使用需求。類和接口相比,類就是一個(gè)實(shí)體,有屬性和方法,而接口更傾向于一組方法。舉個(gè)例子,就拿斗羅大陸的唐三來看,他存在的繼承關(guān)系可能是這樣的:

如何實(shí)現(xiàn)繼承
實(shí)現(xiàn)繼承除了上面用到的extends外,還可以用implements這個(gè)關(guān)鍵字實(shí)現(xiàn)。下面,讓我給你逐一講解一下。
extends關(guān)鍵字
在Java中,類的繼承是單一繼承,也就是說一個(gè)子類只能擁有一個(gè)父類,所以extends只能繼承一個(gè)類。其使用語法為:
class?子類名?extends?父類名{}
例如Dog類繼承Animal類,它是這樣的:
class?Animal{}?//定義Animal類
class?Dog?extends?Animal{}?//Dog類繼承Animal類
子類繼承父類后,就擁有父類的非私有的屬性和方法。如果不明白,請(qǐng)看這個(gè)案例,在IDEA下創(chuàng)建一個(gè)項(xiàng)目,創(chuàng)建一個(gè)test類做測(cè)試,分別創(chuàng)建Animal類和Dog類,Animal作為父類寫一個(gè)sayHello()方法,Dog類繼承Animal類之后就可以調(diào)用sayHello()方法。具體代碼為:
class?Animal?{
????public?void??sayHello()//父類的方法
????{
????????System.out.println("hello,everybody");
????}
}
class?Dog?extends?Animal//繼承animal
{?}
public?class?test?{
????public?static?void?main(String[]?args)?{
???????Dog?dog=new?Dog();
???????dog.sayHello();
????}
}
點(diǎn)擊運(yùn)行的時(shí)候Dog子類可以直接使用Animal父類的方法。

implements 關(guān)鍵字
使用implements 關(guān)鍵字可以變相使Java擁有多繼承的特性,使用范圍為類實(shí)現(xiàn)接口的情況,一個(gè)類可以實(shí)現(xiàn)多個(gè)接口(接口與接口之間用逗號(hào)分開)。Java接口是一系列方法的聲明,一個(gè)接口中沒有方法的具體實(shí)現(xiàn) 。子類實(shí)現(xiàn)接口的時(shí)候必須重寫接口中的方法。
我們來看一個(gè)案例,創(chuàng)建一個(gè)test2類做測(cè)試,分別創(chuàng)建doA接口和doB接口,doA接口聲明sayHello()方法,doB接口聲明eat()方法,創(chuàng)建Cat2類實(shí)現(xiàn)doA和doB接口,并且在類中需要重寫sayHello()方法和eat()方法。具體代碼為:
interface?doA{
?????void?sayHello();
}
interface?doB{
?????void?eat();
????//以下會(huì)報(bào)錯(cuò)?接口中的方法不能具體定義只能聲明
????//public?void?eat(){System.out.println("eating");}
}
class?Cat2?implements??doA,doB{
????@Override//必須重寫接口內(nèi)的方法
????public?void?sayHello()?{
????????System.out.println("hello!");
????}
????@Override
????public?void?eat()?{
????????System.out.println("I'm?eating");
????}
}
public?class?test2?{
????public?static?void?main(String[]?args)?{
????????Cat2?cat=new?Cat2();
????????cat.sayHello();
????????cat.eat();
????}
}
Cat類實(shí)現(xiàn)doA和doB接口的時(shí)候,需要實(shí)現(xiàn)其聲明的方法,點(diǎn)擊運(yùn)行結(jié)果如下,這就是一個(gè)類實(shí)現(xiàn)接口的簡(jiǎn)單案例:

繼承的特點(diǎn)
繼承的主要內(nèi)容就是子類繼承父類,并重寫父類的方法。使用子類的屬性或方法時(shí)候,首先要?jiǎng)?chuàng)建一個(gè)對(duì)象,而對(duì)象通過構(gòu)造方法去創(chuàng)建,在構(gòu)造方法中我們可能會(huì)調(diào)用子父類的一些屬性和方法,所以就需要提前掌握this和super關(guān)鍵字。創(chuàng)建完這個(gè)對(duì)象之后,在調(diào)用重寫父類的方法,并區(qū)別重寫和重載的區(qū)別。所以本節(jié)根據(jù)this、super關(guān)鍵字—>構(gòu)造函數(shù)—>方法重寫—>方法重載的順序進(jìn)行講解。
this和super關(guān)鍵字
this和super關(guān)鍵字是繼承中非常重要的知識(shí)點(diǎn),分別表示當(dāng)前對(duì)象的引用和父類對(duì)象的引用,兩者有很大相似又有一些區(qū)別。
this表示當(dāng)前對(duì)象,是指向自己的引用。
this.屬性 // 調(diào)用成員變量,要區(qū)別成員變量和局部變量
this.() // 調(diào)用本類的某個(gè)方法
this() // 表示調(diào)用本類構(gòu)造方法
super表示父類對(duì)象,是指向父類的引用。
super.屬性 // 表示父類對(duì)象中的成員變量
super.方法() // 表示父類對(duì)象中定義的方法
super() // 表示調(diào)用父類構(gòu)造方法
此外,this和super關(guān)鍵字只能出現(xiàn)在非static修飾的代碼中。
this()和super()都只能在構(gòu)造方法的第一行出現(xiàn),如果使用this()表示調(diào)用當(dāng)前類的其他構(gòu)造方法,使用super()表示調(diào)用父類的某個(gè)構(gòu)造方法,所以兩者只能根據(jù)自己使用需求選擇其一。
寫一個(gè)小案例,創(chuàng)建D1類和子類D2如下:
class?D1{
????public?D1()?{}//無參構(gòu)造
????public?void?sayHello()?{
????????System.out.println("hello");
????}
}
class?D2?extends?D1{
????public?String?name;
????public?D2(){
????????super();//調(diào)用父類構(gòu)造方法
????????this.name="BigSai";//給當(dāng)前類成員變量賦值
????}
????@Override
????public?void?sayHello()?{
????????System.out.println("hello,我是"+this.name);
????}
????public?void?test()
????{
????????super.sayHello();//調(diào)用父類方法
????????this.sayHello();//調(diào)用當(dāng)前類其他方法
????}
}
public?class?test8?{
????public?static?void?main(String[]?args)?{
????????D2?d2=new?D2();
????????d2.test();
????}
}
執(zhí)行的結(jié)果為:

構(gòu)造方法
構(gòu)造方法是一種特殊的方法,它是一個(gè)與類同名的方法。對(duì)象的創(chuàng)建就通過構(gòu)造方法來完成,其主要的功能是完成對(duì)象的初始化。但在繼承中構(gòu)造方法是一種比較特殊的方法(比如不能繼承),所以要了解和學(xué)習(xí)在繼承中構(gòu)造方法的規(guī)則和要求。
構(gòu)造方法可分為有參構(gòu)造和無參構(gòu)造,這個(gè)可以根據(jù)自己的使用需求合理設(shè)置構(gòu)造方法。但繼承中的構(gòu)造方法有以下幾點(diǎn)需要注意:
父類的構(gòu)造方法不能被繼承:
因?yàn)闃?gòu)造方法語法是與類同名,而繼承則不更改方法名,如果子類繼承父類的構(gòu)造方法,那明顯與構(gòu)造方法的語法沖突了。比如Father類的構(gòu)造方法名為Father(),Son類如果繼承Father類的構(gòu)造方法Father(),那就和構(gòu)造方法定義:構(gòu)造方法與類同名沖突了,所以在子類中不能繼承父類的構(gòu)造方法,但子類會(huì)調(diào)用父類的構(gòu)造方法。
子類的構(gòu)造過程必須調(diào)用其父類的構(gòu)造方法:
Java虛擬機(jī)構(gòu)造子類對(duì)象前會(huì)先構(gòu)造父類對(duì)象,父類對(duì)象構(gòu)造完成之后再來構(gòu)造子類特有的屬性,這被稱為內(nèi)存疊加。而Java虛擬機(jī)構(gòu)造父類對(duì)象會(huì)執(zhí)行父類的構(gòu)造方法,所以子類構(gòu)造方法必須調(diào)用super()即父類的構(gòu)造方法。就比如一個(gè)簡(jiǎn)單的繼承案例應(yīng)該這么寫:
class?A{
????public?String?name;
????public?A()?{//無參構(gòu)造
????}
????public?A?(String?name){//有參構(gòu)造
????}
}
class?B?extends?A{
????public?B()?{//無參構(gòu)造
???????super();
????}
????public?B(String?name)?{//有參構(gòu)造
??????//super();
???????super(name);
????}
}
如果子類的構(gòu)造方法中沒有顯示地調(diào)用父類構(gòu)造方法,則系統(tǒng)默認(rèn)調(diào)用父類無參數(shù)的構(gòu)造方法。
你可能有時(shí)候在寫繼承的時(shí)候子類并沒有使用super()調(diào)用,程序依然沒問題,其實(shí)這樣是為了節(jié)省代碼,系統(tǒng)執(zhí)行時(shí)會(huì)自動(dòng)添加父類的無參構(gòu)造方式,如果不信的話我們對(duì)上面的類稍作修改執(zhí)行:

方法重寫(Override)
方法重寫也就是子類中出現(xiàn)和父類中一模一樣的方法(包括返回值類型,方法名,參數(shù)列表),它建立在繼承的基礎(chǔ)上。你可以理解為方法的外殼不變,但是核心內(nèi)容重寫。
在這里提供一個(gè)簡(jiǎn)單易懂的方法重寫案例:
class?E1{
????public?void?doA(int?a){
????????System.out.println("這是父類的方法");
????}
}
class?E2?extends?E1{
????@Override
????public?void?doA(int?a)?{
????????System.out.println("我重寫父類方法,這是子類的方法");
????}
}
其中@Override注解顯示聲明該方法為注解方法,可以幫你檢查重寫方法的語法正確性,當(dāng)然如果不加也是可以的,但建議加上。
對(duì)于重寫,你需要注意以下幾點(diǎn):
從重寫的要求上看:
重寫的方法和父類的要一致(包括返回值類型、方法名、參數(shù)列表) 方法重寫只存在于子類和父類之間,同一個(gè)類中只能重載
從訪問權(quán)限上看:
子類方法不能縮小父類方法的訪問權(quán)限 子類方法不能拋出比父類方法更多的異常 父類的私有方法不能被子類重寫
從靜態(tài)和非靜態(tài)上看:
父類的靜態(tài)方法不能被子類重寫為非靜態(tài)方法 子類可以定義于父類的靜態(tài)方法同名的靜態(tài)方法,以便在子類中隱藏父類的靜態(tài)方法(滿足重寫約束) 父類的非靜態(tài)方法不能被子類重寫為靜態(tài)方法
從抽象和非抽象來看:
父類的抽象方法可以被子類通過兩種途徑重寫(即實(shí)現(xiàn)和重寫) 父類的非抽象方法可以被重寫為抽象方法
當(dāng)然,這些規(guī)則可能涉及一些修飾符,在第三關(guān)中會(huì)詳細(xì)介紹。
方法重載(Overload)
如果有兩個(gè)方法的方法名相同,但參數(shù)不一致,那么可以說一個(gè)方法是另一個(gè)方法的重載。方法重載規(guī)則如下:
被重載的方法必須改變參數(shù)列表(參數(shù)個(gè)數(shù)或類型或順序不一樣) 被重載的方法可以改變返回類型 被重載的方法可以改變?cè)L問修飾符 被重載的方法可以聲明新的或更廣的檢查異常 方法能夠在同一個(gè)類中或者在一個(gè)子類中被重載 無法以返回值類型作為重載函數(shù)的區(qū)分標(biāo)準(zhǔn)
重載可以通常理解為完成同一個(gè)事情的方法名相同,但是參數(shù)列表不同其他條件也可能不同。一個(gè)簡(jiǎn)單的方法重載的例子,類E3中的add()方法就是一個(gè)重載方法。
class?E3{
????public?int?add(int?a,int?b){
????????return?a+b;
????}
????public?double?add(double?a,double?b)?{
????????return?a+b;
????}
????public?int?add(int?a,int?b,int?c)?{
????????return?a+b+c;
????}
}
方法重寫和方法重載的區(qū)別:
方法重寫和方法重載名稱上容易混淆,但內(nèi)容上有很大區(qū)別,下面用一個(gè)表格列出其中區(qū)別:
| 區(qū)別點(diǎn) | 方法重寫 | 方法重載 |
|---|---|---|
| 結(jié)構(gòu)上 | 垂直結(jié)構(gòu),是一種父子類之間的關(guān)系 | 水平結(jié)構(gòu),是一種同類之間關(guān)系 |
| 參數(shù)列表 | 不可以修改 | 可以修改 |
| 訪問修飾符 | 子類的訪問修飾符范圍必須大于等于父類訪問修飾符范圍 | 可以修改 |
| 拋出異常 | 子類方法異常必須是父類方法異常或父類方法異常子異常 | 可以修改 |
繼承與修飾符
Java修飾符的作用就是對(duì)類或類成員進(jìn)行修飾或限制,每個(gè)修飾符都有自己的作用,而在繼承中可能有些特殊修飾符使得被修飾的屬性或方法不能被繼承,或者繼承需要一些其他的條件,下面就詳細(xì)介紹在繼承中一些修飾符的作用和特性。
Java語言提供了很多修飾符,修飾符用來定義類、方法或者變量,通常放在語句的最前端。主要分為以下兩類:
訪問修飾符 非訪問修飾符
這里訪問修飾符主要講解public,protected,default,private四種訪問控制修飾符。非訪問修飾符這里就介紹static修飾符,final修飾符和abstract修飾符。
訪問修飾符
public,protected,default(無修飾詞),private修飾符是面向?qū)ο笾蟹浅V匾闹R(shí)點(diǎn),而在繼承中也需要懂得各種修飾符使用規(guī)則。
首先我們都知道不同的關(guān)鍵字作用域不同,四種關(guān)鍵字的作用域如下:
| 同一個(gè)類 | 同一個(gè)包 | 不同包子類 | 不同包非子類 | |
|---|---|---|---|---|
| private | ? | |||
| default | ? | ? | ||
| protect | ? | ? | ? | |
| public | ? | ? | ? | ? |
private:Java語言中對(duì)訪問權(quán)限限制的最窄的修飾符,一般稱之為“私有的”。被其修飾的屬性以及方法只能被該類的對(duì)象訪問,其子類不能訪問,更不能允許跨包訪問。
default:(也有稱friendly)即不加任何訪問修飾符,通常稱為“默認(rèn)訪問權(quán)限“或者“包訪問權(quán)限”。該模式下,只允許在同一個(gè)包中進(jìn)行訪問。
protected:介于public 和 private 之間的一種訪問修飾符,一般稱之為“保護(hù)訪問權(quán)限”。被其修飾的屬性以及方法只能被類本身的方法及子類訪問,即使子類在不同的包中也可以訪問。
public:Java語言中訪問限制最寬的修飾符,一般稱之為“公共的”。被其修飾的類、屬性以及方法不僅可以跨類訪問,而且允許跨包訪問。
Java 子類重寫繼承的方法時(shí),不可以降低方法的訪問權(quán)限,子類繼承父類的訪問修飾符作用域不能比父類小,也就是更加開放,假如父類是protected修飾的,其子類只能是protected或者public,絕對(duì)不能是default(默認(rèn)的訪問范圍)或者private。所以在繼承中需要重寫的方法不能使用private修飾詞修飾。
如果還是不太清楚可以看幾個(gè)小案例就很容易搞懂,寫一個(gè)A1類中用四種修飾詞實(shí)現(xiàn)四個(gè)方法,用子類A2繼承A1,重寫A1方法時(shí)候你就會(huì)發(fā)現(xiàn)父類私有方法不能重寫,非私有方法重寫使用的修飾符作用域不能變小(大于等于)。

正確的案例應(yīng)該為:
class?A1?{
????private?void?doA(){?}
????void?doB(){}//default
????protected?void?doC(){}
????public?void?doD(){}
}
class?A2?extends?A1{
????@Override
????public?void?doB()?{?}//繼承子類重寫的方法訪問修飾符權(quán)限可擴(kuò)大
????@Override
????protected?void?doC()?{?}//繼承子類重寫的方法訪問修飾符權(quán)限可和父類一致
????@Override
????public?void?doD()?{?}//不可用protected或者default修飾
}
還要注意的是,繼承當(dāng)中子類拋出的異常必須是父類拋出的異?;蚋割悞伋霎惓5淖赢惓?/strong>。下面的一個(gè)案例四種方法測(cè)試可以發(fā)現(xiàn)子類方法的異常不可大于父類對(duì)應(yīng)方法拋出異常的范圍。

正確的案例應(yīng)該為:
class?B1{
????public?void?doA()?throws?Exception{}
????public?void?doB()?throws?Exception{}
????public?void?doC()?throws?IOException{}
????public?void?doD()?throws?IOException{}
}
class?B2?extends?B1{
????//異常范圍和父類可以一致
????@Override
????public?void?doA()?throws?Exception?{?}
????//異常范圍可以比父類更小
????@Override
????public?void?doB()?throws?IOException?{?}
????//異常范圍?不可以比父類范圍更大
????@Override
????public?void?doC()?throws?IOException?{?}//不可拋出Exception等比IOException更大的異常
????@Override
????public?void?doD()?throws?IOException?{?}
}
非訪問修飾符
訪問修飾符用來控制訪問權(quán)限,而非訪問修飾符每個(gè)都有各自的作用,下面針對(duì)static、final、abstract修飾符進(jìn)行介紹。
static 修飾符
static 翻譯為“靜態(tài)的”,能夠與變量,方法和類一起使用,稱為靜態(tài)變量,靜態(tài)方法(也稱為類變量、類方法)。如果在一個(gè)類中使用static修飾變量或者方法的話,它們可以直接通過類訪問,不需要?jiǎng)?chuàng)建一個(gè)類的對(duì)象來訪問成員。
我們?cè)谠O(shè)計(jì)類的時(shí)候可能會(huì)使用靜態(tài)方法,有很多工具類比如Math,Arrays等類里面就寫了很多靜態(tài)方法。static修飾符的規(guī)則很多,這里僅僅介紹和Java繼承相關(guān)用法的規(guī)則:
構(gòu)造方法不允許聲明為 static 的。 靜態(tài)方法中不存在當(dāng)前對(duì)象,因而不能使用 this,當(dāng)然也不能使用 super。 靜態(tài)方法不能被非靜態(tài)方法重寫(覆蓋) 靜態(tài)方法能被靜態(tài)方法重寫(覆蓋)
可以看以下的案例證明上述規(guī)則:

源代碼為:
class?C1{
????public??int?a;
????public?C1(){}
???//?public?static?C1(){}//?構(gòu)造方法不允許被聲明為static
????public?static?void?doA()?{}
????public?static?void?doB()?{}
}
class?C2?extends?C1{
????public?static??void?doC()//靜態(tài)方法中不存在當(dāng)前對(duì)象,因而不能使用this和super。
????{
????????//System.out.println(super.a);
????}
????public?static?void?doA(){}//靜態(tài)方法能被靜態(tài)方法重寫
???//?public?void?doB(){}//靜態(tài)方法不能被非靜態(tài)方法重寫
}
final修飾符
final變量:
final 表示"最后的、最終的"含義,變量一旦賦值后,不能被重新賦值。被 final 修飾的實(shí)例變量必須顯式指定初始值(即不能只聲明)。final 修飾符通常和 static 修飾符一起使用來創(chuàng)建類常量。
final 方法:
父類中的 final 方法可以被子類繼承,但是不能被子類重寫。聲明 final 方法的主要目的是防止該方法的內(nèi)容被修改。
final類:
final 類不能被繼承,沒有類能夠繼承 final 類的任何特性。
所以無論是變量、方法還是類被final修飾之后,都有代表最終、最后的意思。內(nèi)容無法被修改。
abstract 修飾符
abstract 英文名為“抽象的”,主要用來修飾類和方法,稱為抽象類和抽象方法。
抽象方法:有很多不同類的方法是相似的,但是具體內(nèi)容又不太一樣,所以我們只能抽取他的聲明,沒有具體的方法體,即抽象方法可以表達(dá)概念但無法具體實(shí)現(xiàn)。
抽象類:有抽象方法的類必須是抽象類,抽象類可以表達(dá)概念但是無法構(gòu)造實(shí)體的類。
抽象類和抽象方法內(nèi)容和規(guī)則比較多。這里只提及一些和繼承有關(guān)的用法和規(guī)則:
抽象類也是類,如果一個(gè)類繼承于抽象類,就不能繼承于其他的(類或抽象類) 子類可以繼承于抽象類,但是一定要實(shí)現(xiàn)父類們所有abstract的方法。如果不能完全實(shí)現(xiàn),那么子類也必須被定義為抽象類 只有實(shí)現(xiàn)父類的所有抽象方法,才能是完整類。

比如我們可以這樣設(shè)計(jì)一個(gè)People抽象類以及一個(gè)抽象方法,在子類中具體完成:
abstract?class?People{
????public?abstract?void?sayHello();//抽象方法
}
class?Chinese?extends?People{
????@Override
????public?void?sayHello()?{//實(shí)現(xiàn)抽象方法
????????System.out.println("你好");
????}
}
class?Japanese?extends?People{
????@Override
????public?void?sayHello()?{//實(shí)現(xiàn)抽象方法
????????System.out.println("口你七哇");
????}
}
class?American?extends?People{
????@Override
????public?void?sayHello()?{//實(shí)現(xiàn)抽象方法
????????System.out.println("hello");
????}
}
Object類和轉(zhuǎn)型
提到Java繼承,不得不提及所有類的根類:Object(java.lang.Object)類,如果一個(gè)類沒有顯式聲明它的父類(即沒有寫extends xx),那么默認(rèn)這個(gè)類的父類就是Object類,任何類都可以使用Object類的方法,創(chuàng)建的類也可和Object進(jìn)行向上、向下轉(zhuǎn)型,所以O(shè)bject類是掌握和理解繼承所必須的知識(shí)點(diǎn)。而Java向上和向下轉(zhuǎn)型在Java中運(yùn)用很多,也是建立在繼承的基礎(chǔ)上,所以Java轉(zhuǎn)型也是掌握和理解繼承所必須的知識(shí)點(diǎn)。
Object類概述
Object是類層次結(jié)構(gòu)的根類,所有的類都隱式的繼承自O(shè)bject類。
Java所有的對(duì)象都擁有Object默認(rèn)方法
Object類的構(gòu)造方法有一個(gè),并且是無參構(gòu)造
Object是java所有類的父類,是整個(gè)類繼承結(jié)構(gòu)的頂端,也是最抽象的一個(gè)類。像toString()、equals()、hashCode()、wait()、notify()、getClass()等都是Object的方法。你以后可能會(huì)經(jīng)常碰到,但其中遇到更多的就是toString()方法和equals()方法,我們經(jīng)常需要重寫這兩種方法滿足我們的使用需求。
**toString()**方法表示返回該對(duì)象的字符串,由于各個(gè)對(duì)象構(gòu)造不同所以需要重寫,如果不重寫的話默認(rèn)返回類名@hashCode格式。
如果重寫toString()方法后直接調(diào)用toString()方法就可以返回我們自定義的該類轉(zhuǎn)成字符串類型的內(nèi)容輸出,而不需要每次都手動(dòng)的拼湊成字符串內(nèi)容輸出,大大簡(jiǎn)化輸出操作。
**equals()方法主要比較兩個(gè)對(duì)象是否相等,因?yàn)閷?duì)象的相等不一定非要嚴(yán)格要求兩個(gè)對(duì)象地址上的相同,有時(shí)內(nèi)容上的相同我們就會(huì)認(rèn)為它相等,比如String 類就重寫了euqals()**方法,通過字符串的內(nèi)容比較是否相等。

向上轉(zhuǎn)型
向上轉(zhuǎn)型 : 通過子類對(duì)象(小范圍)實(shí)例化父類對(duì)象(大范圍),這種屬于自動(dòng)轉(zhuǎn)換。用一張圖就能很好地表示向上轉(zhuǎn)型的邏輯:

父類引用變量指向子類對(duì)象后,只能使用父類已聲明的方法,但方法如果被重寫會(huì)執(zhí)行子類的方法,如果方法未被重寫那么將執(zhí)行父類的方法。
向下轉(zhuǎn)型
向下轉(zhuǎn)型 : 通過父類對(duì)象(大范圍)實(shí)例化子類對(duì)象(小范圍),在書寫上父類對(duì)象需要加括號(hào)()強(qiáng)制轉(zhuǎn)換為子類類型。但父類引用變量實(shí)際引用必須是子類對(duì)象才能成功轉(zhuǎn)型,這里也用一張圖就能很好表示向上轉(zhuǎn)型的邏輯:

子類引用變量指向父類引用變量指向的對(duì)象后(一個(gè)Son()對(duì)象),就完成向下轉(zhuǎn)型,就可以調(diào)用一些子類特有而父類沒有的方法 。
在這里寫一個(gè)向上轉(zhuǎn)型和向下轉(zhuǎn)型的案例:
Object?object=new?Integer(666);//向上轉(zhuǎn)型
Integer?i=(Integer)object;//向下轉(zhuǎn)型Object->Integer,object的實(shí)質(zhì)還是指向Integer
String?str=(String)object;//錯(cuò)誤的向下轉(zhuǎn)型,雖然編譯器不會(huì)報(bào)錯(cuò)但是運(yùn)行會(huì)報(bào)錯(cuò)
子父類初始化順序
在Java繼承中,父子類初始化先后順序?yàn)椋?/p>
父類中靜態(tài)成員變量和靜態(tài)代碼塊
子類中靜態(tài)成員變量和靜態(tài)代碼塊
父類中普通成員變量和代碼塊,父類的構(gòu)造函數(shù)
子類中普通成員變量和代碼塊,子類的構(gòu)造函數(shù)
總的來說,就是靜態(tài)>非靜態(tài),父類>子類,非構(gòu)造函數(shù)>構(gòu)造函數(shù)。同一類別(例如普通變量和普通代碼塊)成員變量和代碼塊執(zhí)行從前到后,需要注意邏輯。
這個(gè)也不難理解,靜態(tài)變量也稱類變量,可以看成一個(gè)全局變量,靜態(tài)成員變量和靜態(tài)代碼塊在類加載的時(shí)候就初始化,而非靜態(tài)變量和代碼塊在對(duì)象創(chuàng)建的時(shí)候初始化。所以靜態(tài)快于非靜態(tài)初始化。
而在創(chuàng)建子類對(duì)象的時(shí)候需要先創(chuàng)建父類對(duì)象,所以父類優(yōu)先于子類。
而在調(diào)用構(gòu)造函數(shù)的時(shí)候,是對(duì)成員變量進(jìn)行一些初始化操作,所以普通成員變量和代碼塊優(yōu)于構(gòu)造函數(shù)執(zhí)行。
至于更深層次為什么這個(gè)順序,就要更深入了解JVM執(zhí)行流程啦。下面一個(gè)測(cè)試代碼為:
class?Father{
????public?Father()?{
????????System.out.println(++b1+"父類構(gòu)造方法");
????}//父類構(gòu)造方法?第四
????static?int?a1=0;//父類static?第一?注意順序
????static?{
????????System.out.println(++a1+"父類static");
????}
????int?b1=a1;//父類成員變量和代碼塊?第三
????{
????????System.out.println(++b1+"父類代碼塊");
????}
}
class?Son?extends?Father{
????public?Son()?{
????????System.out.println(++b2+"子類構(gòu)造方法");
????}//子類構(gòu)造方法?第六
????static?{//子類static第二步
????????System.out.println(++a1+"子類static");
????}
????int?b2=b1;//子類成員變量和代碼塊?第五
????{
????????System.out.println(++b2?+?"子類代碼塊");
????}
}
public?class?test9?{
????public?static?void?main(String[]?args)?{
????????Son?son=new?Son();
????}
}
執(zhí)行結(jié)果:

結(jié)語
好啦,本次繼承就介紹到這里啦,Java面向?qū)ο笕筇卣髦焕^承——優(yōu)秀的你已經(jīng)掌握。再看看Java面向?qū)ο笕筇匦裕悍庋b、繼承、多態(tài)。最后問你能大致了解它們的特征嘛?
封裝:是對(duì)類的封裝,封裝是對(duì)類的屬性和方法進(jìn)行封裝,只對(duì)外暴露方法而不暴露具體使用細(xì)節(jié),所以我們一般設(shè)計(jì)類成員變量時(shí)候大多設(shè)為私有而通過一些get、set方法去讀寫。
繼承:子類繼承父類,即“子承父業(yè)”,子類擁有父類除私有的所有屬性和方法,自己還能在此基礎(chǔ)上拓展自己新的屬性和方法。主要目的是復(fù)用代碼。
多態(tài):多態(tài)是同一個(gè)行為具有多個(gè)不同表現(xiàn)形式或形態(tài)的能力。即一個(gè)父類可能有若干子類,各子類實(shí)現(xiàn)父類方法有多種多樣,調(diào)用父類方法時(shí),父類引用變量指向不同子類實(shí)例而執(zhí)行不同方法,這就是所謂父類方法是多態(tài)的。
最后送你一張圖捋一捋其中的關(guān)系吧。
題外話: 目前小哈正在個(gè)人博客(新搭建的網(wǎng)站,域名就是犬小哈的拼音)?www.quanxiaoha.com?上更新《Go語言教程》,畢竟Go自帶天然的并發(fā)優(yōu)勢(shì),后端的同學(xué)還是要學(xué)一下的,這個(gè)教程系列小哈會(huì)一直更新下去,目前已經(jīng)更新到 Go語言的基礎(chǔ)語法了,歡迎小伙伴們?cè)L問哦~
END
有熱門推薦?
1.?SQL 優(yōu)化極簡(jiǎn)法則,還有誰不會(huì)?
2.?SpringBoot+MyBatis+MySQL讀寫分離(實(shí)例)
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)


