Java基础-面向对象第二特征之继承(Inheritance)
Java基礎(chǔ)-面向?qū)ο蟮诙卣髦^承(Inheritance)
作者:尹正杰
版權(quán)聲明:原創(chuàng)作品,謝絕轉(zhuǎn)載!否則將追究法律責(zé)任。
?
?
?
一.繼承的概述
在現(xiàn)實生活中,繼承一般指的是子女繼承父輩的財產(chǎn)。在程序中,繼承描述的是事物之間的所屬關(guān)系,通過繼承可以使多種事物之間形成了一種關(guān)系體系。例如公司中的研發(fā)部員工,運維部員工,人事部員工都屬于員工,程序中便可以描述為研發(fā)部員工和維護部員工繼承自員工,同理,JavaEE工程師和Python工程師繼承自研發(fā)部員工,而網(wǎng)絡(luò)運維工程師和系統(tǒng)運維工程師繼承自維護部員工,人事部經(jīng)理和培訓(xùn)專員繼承自人事部員工。這些員工之間會形成一個繼承體系,具體如下圖所示:
?
在Java中,類的繼承是指在一個現(xiàn)有的基礎(chǔ)上去構(gòu)建一個新的類,構(gòu)建出來的新類被稱作子類,現(xiàn)有類被稱作父類,子類會自動擁有父類所有可繼承的屬性和方法。
?
二.繼承的使用方式(extends)
1>.什么是父類和子類
多個類中存在相同屬性和行為時,將這些內(nèi)容抽取到單獨一個類中,那么多個類無需再定義這些屬性和行為,只要從抽取出來的那個類擴展(extends)即可,需要擴展的類稱為子類(也叫派生類),抽取出來的那個類稱為父類(也可以叫超類或者基類)。
2>.繼承的定義格式和使用
繼承一個類使用關(guān)鍵字extends來實現(xiàn),格式為:"class 子類 extends 父類{}",下面有一個很簡單的繼承案例,如下:
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 8 //定義員工類:成員變量(name),成員方法(work) 9 public class Employee { 10 String name; 11 12 public void work(){ 13 System.out.println("員工在工作....."); 14 } 15 } Employee.java 文件內(nèi)容 1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 //定義研發(fā)部員工,屬于員工中的一種,研發(fā)員工繼承員工類,使用關(guān)鍵字extends。 8 public class Development extends Employee{ 9 public void print(){ 10 System.out.println(name); 11 } 12 } Development.java 文件內(nèi)容 1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 public class Test { 8 public static void main(String[] args){ 9 Development d = new Development(); 10 d.name = "yinzhengjie"; 11 d.print(); 12 } 13 }3>.繼承的好處
a>.繼承的出現(xiàn)提高了代碼的復(fù)用性,提高了軟件開發(fā)效率;
b>.繼承的出現(xiàn)讓類與類之間產(chǎn)生了關(guān)系,提供了多態(tài)的前提(多態(tài)本篇博客暫時不涉及)。
4>.繼承的注意事項
a>.在Java中,類只支持單繼承,不允許多繼承,也就是說一個類只能有一個直接父類,不可以有多個父類。
b>.Java支持多層繼承,多重繼承(在Java中,子類和父類是一種相對概念,也就是說一個類是某個父類的同時,也可以是另一個類的子類。例如:C類:同時擁有A和B的屬性和方法;B類:只擁有A類屬性和方法)
?
c>.多個類中可以繼承一個父類。
5>.繼承的體系
? 繼承也有自己的體系,我們可以下面的一張圖來說明特點。
6>.繼承后子類父類成員變量的特點
子類的對象,在調(diào)用成員變量的時候遵循以下順序:
a>.先在子類的方法中找(方法是壓棧執(zhí)行,里面的變量存在棧內(nèi)存中,屬于基本數(shù)據(jù)類型);
b>.再找子類的成員變量,子類自己有就使用自己的(類中的成員變量屬于引用數(shù)據(jù)類型,成員變量存在堆內(nèi)存中,需要用到關(guān)鍵字this);
c>.最后再找父類的成員變量,子類沒有的話就調(diào)用父類的(需要用到關(guān)鍵字super);
d>.如果以上步驟都沒有找到,就會編譯報錯!
說白了,就是遵循就近原則!下面的3個文件是我實際測試的代碼。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 public class Father { 8 int age = 40; 9 } Father.java 文件內(nèi)容 1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 public class Son extends Father { 8 int age = 18; //該變量存在與堆內(nèi)存中。 9 10 public void print() { 11 int age = 20; //該變量隨著方法彈棧執(zhí)行,因此改變量存在棧內(nèi)存。 12 System.out.println("當(dāng)前作用域的age變量的值為:"+age); 13 System.out.println("本類中成員變量age的值為:"+this.age); 14 System.out.println("父類中成員變量age的值為:"+super.age); 15 } 16 } Son.java 文件內(nèi)容 1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 public class Test { 8 public static void main(String[] args){ 9 Son s = new Son(); 10 s.print(); 11 } 12 } 13 14 15 16 /* 17 以上代碼此時結(jié)果如下: 18 當(dāng)前作用域的age變量的值為:20 19 本類中成員變量age的值為:18 20 父類中成員變量age的值為:40 21 */ Test.java 文件內(nèi)容7>.子類重寫父類方法(Overrride|Overwrite)
子類中,出現(xiàn)了和父類一模一樣的方法(函數(shù)的返回值類型,函數(shù)名,參數(shù)列表都一樣)的時候,子類重寫父類的方法,也可以說是子類覆蓋了父類的方法(當(dāng)子類需要父類功能,而功能主體子類有自己特有內(nèi)容時,可以重寫父類中的方法,這樣即沿襲了父類的功能又定義了子類特有的內(nèi)容。)。方法的重寫是指兩個類中的名詞喲,而方法的重載指的的同一個類中的名詞,大家不要混淆喲!
a>.子類中出現(xiàn)與父類一模一樣的方法時,會出現(xiàn)覆蓋操作,也稱為重寫或者復(fù)寫;
b>.父類中私有方法不可以被重寫,當(dāng)然,私有方法也不能被繼承;
c>.在子類重寫方法中,繼續(xù)使用被重寫的方法可以通過super.方法名(...);
d>.覆蓋注意事項:覆蓋時,子類方法權(quán)限一定要大于父類方法權(quán)限;除了訪問權(quán)限之外,其它部分和父類方法保持一致;靜態(tài)只能覆蓋靜態(tài)。
方法重寫是應(yīng)該注意,子類方法覆蓋父類方法,必須要保證權(quán)限大于等于父類權(quán)限。下面的三個文件就是一個簡單的重寫案例:
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 public class Father { 8 public void print() { 9 System.out.println("父類的print()方法!"); 10 } 11 } Father.java 文件內(nèi)容 1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 public class Son extends Father { 8 public void print() { 9 System.out.println("子類的print()方法!"); 10 } 11 } Son.java 文件內(nèi)容 1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 public class Test { 8 public static void main(String[] args){ 9 Son s = new Son(); 10 s.print(); 11 } 12 } 13 14 15 16 /* 17 以上代碼此時結(jié)果如下: 18 子類的print()方法! 19 */ Test.java 文件內(nèi)容總結(jié):當(dāng)一個類中是另一個類中的一種時,可以通繼承,來繼承屬性和功能。如果父類具備的功能內(nèi)容需要子類特殊定義時,進行方法重寫。
8>.子類的實例化過程
a>.子類中所有的構(gòu)造方法默認(rèn)都會訪問父類中空參構(gòu)造的構(gòu)造方法,因為每一個構(gòu)造方法的第一行都有一條默認(rèn)的語句super(),除非第一行用this或super顯式調(diào)用了其它的構(gòu)造方法;
b>.子類會具備父類中的數(shù)據(jù),所以要先明確父類是如何對這些數(shù)據(jù)初始化的,也就是父類對象必須在子類對象初始化前完成初始化操作(沒有父類,就沒有子類);
c>.當(dāng)父類中沒有空參構(gòu)造方法時,子類的構(gòu)造方法必須通過this或者super語句制定要訪問的構(gòu)造方法;
?
三.抽象類的使用方式(abstract)
1>.抽象類概述
a>.抽象定義
抽象就是從多個事物中將共性,本質(zhì)的內(nèi)容抽取出來(例如:狼和狗共性都是犬科,犬科就是抽象出來的概念)。
b>.抽象類
Java中可以定義沒有方法體的方法,該方法的具體實現(xiàn)由子類完成,該方法稱為抽象方法,包含抽象方法的類就是抽象類。
c>.抽象方法的由來
多個對象都具備相同的功能,但是功能具體內(nèi)容有所不同,那么在抽取過程中,值抽取了功能定義,并未抽取功能主體,那么只有功能聲明,沒有功能主體的方法稱為抽象方法。(例如:狼和狗都有吼叫的方法,可是吼叫的內(nèi)容是不一樣的,所以抽象出啦ID全科雖然有吼叫的功能,但是并不明確吼叫的細(xì)節(jié)。)
2>.抽象類的由來
如果在父類中定義一個各個子類都有不同實現(xiàn)的方法,那么子類需要重寫這個方法,為什么父類還要定義這個方法呢?直接去掉不行么?答案是肯定的,去掉當(dāng)然可以啊,只不過這樣就沒法使用多態(tài)啦!由于多態(tài)的要求:父類引用指向子類實例,但父類不能調(diào)用子類獨有的方法,為了使用多態(tài),父類中必須定義有這個方法,但是方法的實現(xiàn)又會被子類重寫。
為了既能使用多態(tài),又能省去在父類中現(xiàn)實的麻煩,Java提供了抽象方法,在父類中只定義方法簽名,不用寫方法的實現(xiàn)。
3>.抽象類的特點
a>.抽象類和抽象方法必須使用abstract關(guān)鍵字來修飾;
b>.抽象方法只有方法聲明,沒有方法體,定義在抽象類中(格式:“修飾符? abstract? ?返回值類型? ?方法名(形參列表);”);
c>.抽象類不可以被實例化,換句話說,就是抽象類不能用new 創(chuàng)建抽象類的對象(從理論上來說:抽象類是具體事物抽取出來的,本身是不具體的,沒有對應(yīng)的實例,例如:犬科是一種抽象的概念,真正存在的是狼和狗。從調(diào)用角度來說:假設(shè)抽象類可以創(chuàng)建對象,那么調(diào)用其抽象方法將變得毫無意義。);
d>.抽象類通過子類實例化,如果子類繼承了抽象類,重寫了一部分的抽象方法,這個子類還是抽象類(換句話說,子類需要實現(xiàn)所有的抽象方法后才可以創(chuàng)建對象,否在該子類依然是抽象類)。
4>.抽象類的應(yīng)用案例
a>.雇員示例
需求:
公司中程序員有姓名,工號,薪水,工作內(nèi)容。
項目經(jīng)理除了有姓名,工號,薪水,還有獎金,工作內(nèi)容。
程序員和項目經(jīng)理都是雇員,但是工作內(nèi)容卻不同,所以工作反方應(yīng)該定義成抽象反方。
接下來,我們就來實現(xiàn)一下這個案例:
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 //雇員抽象類,定義:姓名,工號,薪水 8 public abstract class Employee{ 9 private String name; 10 private String id; 11 private int salary; 12 13 public void setName(String name){ 14 this.name = name; 15 } 16 public String getName(){ 17 return name; 18 } 19 public void setId(String id){ 20 this.id = id; 21 } 22 public String getId(){ 23 return id; 24 } 25 public void setSalary(int salary){ 26 this.salary = salary; 27 } 28 public int getSalary(){ 29 return salary; 30 } 31 32 public void show(){ 33 System.out.printf("姓名:%s\n工號:%s\n薪資:%d\n",name,id,salary); 34 } 35 36 //抽象方法work 37 public abstract void work(); 38 } Employee.java 抽象類定義 1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 //項目經(jīng)理類 8 public class Manager extends Employee{ 9 //獎金 10 private int money; 11 public void setMoney(int money){ 12 this.money = money; 13 } 14 public int getMoney(){ 15 return money; 16 } 17 //重寫show方法 18 public void show(){ 19 System.out.printf("姓名:%s\n工號:%s\n薪資:%d\n獎金:%d\n",getName(),getId(),getSalary(),getMoney()); 20 } 21 22 //實現(xiàn)抽象方法 23 public void work(){ 24 System.out.println("項目經(jīng)理開會..."); 25 } 26 } Manager.java 子類定義 1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 //程序員類 8 public class Programmer extends Employee{ 9 //實現(xiàn)抽象方法 10 public void work(){ 11 System.out.println("程序員寫代碼..."); 12 } 13 } Programmer.java 子類定義 1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 //雇員測試類 8 public class EmployeeDemo{ 9 public static void main(String[] args){ 10 Programmer p = new Programmer(); 11 p.setName("tom"); 12 p.setId("9527"); 13 p.setSalary(8000); 14 p.show(); //調(diào)用的是抽象類(Employee)的方法 15 p.work(); //調(diào)用的是程序員類(Programmer)實現(xiàn)抽象方法的方法 16 17 Manager m = new Manager(); 18 m.setName("John"); 19 m.setId("0001"); 20 m.setSalary(7000); 21 m.setMoney(8000); 22 m.show(); //調(diào)用的是項目經(jīng)理類(Manager)重寫的方法 23 m.work(); //調(diào)用的是項目經(jīng)理類(Manager)實現(xiàn)抽象方法的方法 24 } 25 } 26 27 28 29 /* 30 以上代碼測試結(jié)果如下: 31 姓名:tom 32 工號:9527 33 薪資:8000 34 程序員寫代碼... 35 姓名:John 36 工號:0001 37 薪資:7000 38 獎金:8000 39 項目經(jīng)理開會... 40 */ EmployeeDemo.java 測試類文件內(nèi)容b>.抽象類作為形參
抽象類作為形參,此時需要傳入的參數(shù)是此抽象類實現(xiàn)的一個對象。抽象類作為返回值類型,實際上返回的是此抽象類子類的一個對象。下面的簡單的代碼實現(xiàn):
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 //定義一個抽象類A 8 abstract class A{ 9 public abstract void show(); 10 } 11 //子類B繼承抽象類A 12 class B extends A{ 13 //子類B實現(xiàn)父類的抽象方法 14 public void show(){ 15 System.out.println("B.show()..."); 16 } 17 } 18 //子類C繼承抽象類A 19 class C extends A{ 20 //子類C實現(xiàn)父類的抽象方法 21 public void show(){ 22 System.out.println("C.show()..."); 23 } 24 } 25 //定義將抽象類A作為形參的類 26 class ParameterAbstract{ 27 public void ParameterAbstract(A a){ 28 //此時需要傳入的參數(shù)是此抽象類的實現(xiàn)類的一個對象,也就是說,此時的a一定是一個A的子類對象, 29 a.show(); 30 } 31 public A getA(){ 32 B b = new B(); 33 //實際上返回的是此抽象類子類的一個對象 34 return b; 35 } 36 } 37 38 public class AbstractDemo{ 39 public static void main(String[] args){ 40 B b = new B(); 41 //用匿名對象的方式調(diào)用ParameterAbstract方法,傳入的參數(shù)是抽象類實現(xiàn)子類的一個對象 42 new ParameterAbstract().ParameterAbstract(b); 43 44 C c = new C(); 45 ParameterAbstract t = new ParameterAbstract(); 46 //調(diào)用ParameterAbstract類的構(gòu)造方法 47 t.ParameterAbstract(c); 48 } 49 } 50 51 52 53 54 /* 55 以上代碼執(zhí)行結(jié)果如下: 56 B.show()... 57 C.show()... 58 */5>.抽象類的細(xì)節(jié)
? a>.抽象類可以不可以不包含任何抽象方法?
答:可以,此時這個類的作用就是不讓外界實例化。
b>.抽象類能否有構(gòu)造方法?
答:抽象類中可以有成員變量,而構(gòu)造方法的作用就是對成員變量初始化的,所以,可以有構(gòu)造方法。
c>.抽象類關(guān)鍵字abstract不可以和那些關(guān)鍵字共存?
答:第一個關(guān)鍵字是final(final方法不能被子類重寫,而抽象反方就是需要子類重寫的,兩者沖突);第二個關(guān)鍵字是static(靜態(tài)可以通過類名調(diào)用,但是本類中并沒有真正的反方體);第三個關(guān)鍵字是private(私有的方法子類訪問不到,無法實現(xiàn))。
?
?
四.面向?qū)ο蟪S玫年P(guān)鍵字介紹
1>.final
繼承的出現(xiàn)提高了代碼的復(fù)用性,并方便開發(fā)。但隨之也有問題,有些類在描述之后,不想被繼承,或者有些類中的部分方法功能是固定的,不想讓子類重寫。可是當(dāng)子類繼承了這些特殊類之后,就可以對其中的方法進行重寫,那怎么解決呢?
要解決上述的這些問題,需要使用到一個關(guān)鍵字final,final的意思為最終,不可變。final是個修飾符,它可以用來修飾類,類的成員(不包括構(gòu)造方法),以及局部變量。
a>.final修飾類則為最終類,即不可以被繼承,但是可以繼承其它類。
b>.final修飾的方法不可以被覆蓋,但父類中沒有被final修飾方法,子類覆蓋(重寫)后可以加final。
c>.final修飾的變量稱為常量,這些變量只能賦值一次。
d>.final修飾的引用類型的變量值為對象地址值,地址值就不能更改,但是地址對象的屬性可以修改;
e>.final修飾成員變量,需要在創(chuàng)建對象前賦值,否則報錯(構(gòu)造方法,是創(chuàng)建對象中的事情,可以為成員變量賦值,set方法是創(chuàng)建對象之后的事情,不能為final修飾的成員變量賦值喲!);
?
2>.this
作用:this代表當(dāng)前正在調(diào)用方法的對象
使用場景: a>.setXxx方法中對成員變量賦值:區(qū)分成員變量和局部變量;
b>.構(gòu)造方法互相調(diào)用(注意:構(gòu)造方法中使用this或者super調(diào)用其它構(gòu)造方法的話必須是構(gòu)造方法的第一條語句);
c>.方法中調(diào)用本類其它方法(“this(....);”);
3>.super
super和this的用法類似,this代表本類對象的引用,super代表父類對象的內(nèi)存的標(biāo)識
?
使用場景:a>.子父類出現(xiàn)同名成員時,用super進行區(qū)分(“super.成員變量”或者“super.成員方法()”);
? ? b>.子類使用super調(diào)用父類的構(gòu)造方法(“super(....);”)
4>.static
在定義類的時候,類中都會有相應(yīng)的屬性和方法。而屬性和方法都是通過創(chuàng)建本類對象調(diào)用的。當(dāng)在調(diào)用對象的某個方法,這個方法沒有訪問到對象的特有數(shù)據(jù)時,方法創(chuàng)建這個對象有些多余。可是不創(chuàng)建對象,方法又調(diào)用不了,這是就會想,那么我們能不能不創(chuàng)建對象,就可以調(diào)用方法呢?
可以的,我們可以通過關(guān)鍵字static來實現(xiàn),static它是靜態(tài)修飾符,一般用來修飾類中的成員。
a>.被static修飾的成員變量屬于類(隨著類的加載而加載),不屬于這個類的某個對象(也就是說,多個對象在訪問或修改static修飾的成員變量時,其中一個對象將static成員變量值進行了修改,其它對象中的static成員變量值跟著改變,即多個對象共享同一個static成員變量,這時被static修飾的成員變量并不在堆中,而是在方法區(qū)中保存);
b>.被static修飾的成員可以直接通過類名的方式訪問;
c>.優(yōu)先與對象存在(也就是說用static修飾的方法不能直接調(diào)用非靜態(tài)的方法,如果你非要在靜態(tài)方法中調(diào)用非靜態(tài)方法,那就只能現(xiàn)在靜態(tài)方法中創(chuàng)建一個對象,然后通過調(diào)用對象中的方法的形式調(diào)用非靜態(tài)方法);
d>.靜態(tài)方法中不能寫this也不能寫super;
f>.靜態(tài)方法只能訪問靜態(tài)成員(變量,方法),當(dāng)然靜態(tài)方法也可以間接訪問非靜態(tài)方法(需要現(xiàn)在靜態(tài)中new出來一個對象,通過對象調(diào)用相應(yīng)的方法即可),非靜態(tài)的方法可以訪問靜態(tài)成員;
g>.被這個類的所有對象共享;
什么時候使用static呢?推薦方式:如果方法沒有調(diào)用過非靜態(tài)成員,建議將方法定義為靜態(tài)!
?
5>.靜態(tài)變量和實例變量的區(qū)別
a>.所屬不同
靜態(tài)變量屬于類,也稱為類變量。實例變量屬于對象,也稱為對象(實例)變量。
b>.在內(nèi)存中的位置不同
靜態(tài)變量在方法區(qū)中的靜態(tài)區(qū),實例變量在堆內(nèi)存。
c>.生命周期不同
靜態(tài)變量隨著類的加載而加載,隨著類的消失而消失,實例變量隨著對象的創(chuàng)建而存在,隨著對象的消失而消失。
d>.調(diào)用方法不同
靜態(tài)變量可以通過類名和對象名兩名方式調(diào)動,推薦使用類名調(diào)用實例變量只能使用對象名的方式調(diào)用。
?
轉(zhuǎn)載于:https://www.cnblogs.com/yinzhengjie/p/8784602.html
總結(jié)
以上是生活随笔為你收集整理的Java基础-面向对象第二特征之继承(Inheritance)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于 WebSocket 的 MQTT
- 下一篇: velocity模板引擎 -- java