更新時(shí)間:2022-08-25 來(lái)源:黑馬程序員 瀏覽量:
前言 :
Hello , 各位同學(xué)朋友大家好啊, 今天給大家分享的技術(shù)呢, 是面向?qū)ο笕筇卣髦坏睦^承,我們今天主要按照以下幾個(gè)點(diǎn), 展開(kāi)繼承的講解。
目錄 :
* 繼承的介紹
* 繼承的好處和弊端
* 繼承中成員訪問(wèn)特點(diǎn) - 成員變量
* 繼承中成員訪問(wèn)特點(diǎn) - 成員方法
* 方法重寫(xiě)
* 繼承中成員訪問(wèn)特點(diǎn) - 構(gòu)造方法
* this 和 super 關(guān)鍵字
繼承的介紹
提到了繼承, 大家想到了什么 ? 是的, 子女繼承到的家產(chǎn), 那家產(chǎn)是存在著一種關(guān)系的, 父親和兒子的關(guān)系 。
對(duì)比生活中的繼承, Java 中的繼承, 其實(shí)就是讓**類(lèi)與類(lèi)**之間產(chǎn)生關(guān)系, 什么關(guān)系 ? 子父類(lèi)關(guān)系, 當(dāng)這種關(guān)系建立起來(lái)之后, 子類(lèi)就可以直接使用父類(lèi)中, **非私有**的成員了。
聊到這個(gè)地方, 很多同學(xué)就在想, 我為什么讓我的類(lèi)與類(lèi)之間建立這種關(guān)系呢 ? 請(qǐng)同學(xué)們先閱讀下方代碼。
假設(shè), 我要編寫(xiě)一款 xxx 公司的人員管理系統(tǒng), 這款管理系統(tǒng)需要對(duì)項(xiàng)目經(jīng)理和程序員的信息做管理, 所以就需要編寫(xiě)如下兩個(gè)類(lèi)
現(xiàn)在大家可以發(fā)現(xiàn), 這兩個(gè)類(lèi)中的東西, 完全是長(zhǎng)一樣的 !!! 那我將來(lái)要是再多一個(gè) Hr 的類(lèi), 對(duì)人事的信息做管理, 這些重復(fù)的代碼, 豈不是要再寫(xiě)一遍 ? 麻煩 !
所以, 這時(shí)候我要用繼承來(lái)優(yōu)化代碼了
我將這些類(lèi)當(dāng)中, [共性] 或者說(shuō)是 [相同] 的內(nèi)容, 抽取到一個(gè)父類(lèi) (Employee 員工 ) 當(dāng)中 , 再讓 Coder 和 Manager 繼承 Employee, 代碼就不需要重復(fù)編寫(xiě)了 , 這就能夠提高代碼的復(fù)用性了 ~
但是, 代碼層面, 我給如何讓 Coder 和 Manager 繼承 Employee 呢 ? 請(qǐng)同學(xué)們繼續(xù)看繼承的格式。
繼承的格式 :
可以看到, 實(shí)現(xiàn)繼承, 我們是通過(guò) extends 關(guān)鍵字進(jìn)行編寫(xiě)的 , 下面我們編寫(xiě)下代碼
示例代碼 :
class Employee { String name; int age; double salary; } class Coder extends Employee { } class Manager extends Employee { } public class Test { public static void main(String[] args) { Coder c = new Coder(); c.name = "張三"; c.age = 23; c.salary = 12000; System.out.println(c.name + "---" + c.age + "---" + c.salary); Manager m = new Manager(); m.name = "李四"; m.age = 24; m.salary = 18000; System.out.println(m.name + "---" + m.age + "---" + m.salary); } } ```
通過(guò)上面的代碼, 同學(xué)們可以發(fā)現(xiàn), Coder 和 Manager 中, 明明什么都沒(méi)寫(xiě), 但是卻能夠訪問(wèn)到父類(lèi) Employee 中的屬性了。
所以, 重復(fù)的代碼就不需要重復(fù)編寫(xiě), 代碼的復(fù)用性就提高了 ~
但是, 我相信有些同學(xué)可能會(huì)想
Q : 按照標(biāo)準(zhǔn) JavaBean 來(lái)說(shuō), 成員變量不應(yīng)該私有化嗎 ? 那如果父類(lèi)中的成員私有了, 子類(lèi)豈不是用不到這些數(shù)據(jù)了。
A : 能想到這個(gè)問(wèn)題非常好, 說(shuō)明之前的知識(shí)掌握的很扎實(shí), 但是繼續(xù)想, 我們私有成員變量之后, 會(huì)提供對(duì)應(yīng)的 set 和 get 方法吧, 這些set \ get 可都是公共的, 子類(lèi)是可以繼承到直接用的。
示例代碼 :
package com.itheima.myextends; class Employee { private String name; private int age; private double salary; public Employee() { } public Employee(String name, int age, double salary) { this.name = name; this.age = age; this.salary = salary; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } } class Coder extends Employee { } class Manager extends Employee { } public class Test { public static void main(String[] args) { Coder c = new Coder(); c.setName("張三"); c.setAge(23); c.setSalary(12000); System.out.println(c.getName() + "---" + c.getAge() + "---" + c.getSalary()); Manager m = new Manager(); m.setName("李四"); m.setAge(24); m.setSalary(18000); System.out.println(m.getName() + "---" + m.getAge() + "---" + m.getSalary()); } } ```
2. 繼承的好處和弊端
好處 :
通過(guò)上面的代碼, 同學(xué)們感受到繼承的什么好處了啊 ? 是的, 非常明顯的發(fā)現(xiàn), 代碼的復(fù)用性提高了。
除此之外呢, 繼承還可以提高代碼的維護(hù)性 , 這什么意思 ?
假設(shè), 我要在這款管理系統(tǒng)中, 加入一個(gè) id 的屬性, 那使用繼承前, 我就需要在每一個(gè)類(lèi)中, 挨個(gè)編寫(xiě), 但是現(xiàn)在有了繼承后, 我只需要在父類(lèi)中編寫(xiě)一個(gè) id, 所有的子類(lèi), 是不是就都具備了這個(gè)屬性了 ? 是的 ! 這就提高了代碼的維護(hù)性了 !
弊端 :
繼續(xù)剛剛的思路, 同學(xué)們可能會(huì)想, 增加一個(gè), 所有子類(lèi)都有了, 那刪除一個(gè), 豈不是所有子類(lèi)都沒(méi)有了啊...
這不就牽一發(fā)而動(dòng)全身了嘛... 是的, 這就是繼承的弊端, 類(lèi)的耦合性太強(qiáng)了。
何時(shí)使用繼承 :
聊到現(xiàn)在, 大家發(fā)現(xiàn)繼承有好處, 也有弊端, 那該如何設(shè)計(jì), 才能讓利大于弊呢 ?
正確示例 :
錯(cuò)誤示例 :
3. 繼承中成員變量訪問(wèn)特點(diǎn)
思考:子父類(lèi)中,如果出現(xiàn)了重名的成員變量,使用的時(shí)候會(huì)優(yōu)先使用??
運(yùn)行效果 :
原因 :
一定要使用父類(lèi)的, 可以使用 super 關(guān)鍵字進(jìn)行區(qū)分
這里同學(xué)們可以先建立起一個(gè)使用思路
this. 調(diào)用本類(lèi)成員
super.調(diào)用父類(lèi)成員
public class Zi extends Fu { int num = 20; public void method(){ System.out.println(super.num); } } ```
4. 繼承中成員方法訪問(wèn)特點(diǎn)
* 思考:子類(lèi)繼承了父類(lèi)之后,是否可以自己繼續(xù)定義方法?
* 回答:當(dāng)然可以, 子類(lèi)繼承父類(lèi), 是可以在繼承到父類(lèi)成員之后, 繼續(xù)加?xùn)|西的
* 問(wèn)題 : 那子父類(lèi)中, 要是出現(xiàn)了重名的成員, 邏輯卻不一樣呢 ?
public class Fu { public void method() { // 父類(lèi)的方法 System.out.println("父類(lèi)method..."); } } class Zi extends Fu { public void method() { System.out.println("子類(lèi)method..."); } } ```
* 運(yùn)行結(jié)果 :
子類(lèi)method... ```
結(jié)論 :
子父類(lèi)中如果出現(xiàn)了重名的成員方法,優(yōu)先使用子類(lèi)的方法 , 但這其實(shí)是子類(lèi)方法對(duì)父類(lèi)方法進(jìn)行了重寫(xiě)。
方法重寫(xiě) Override :
先跟大家聊聊什么是方法重寫(xiě),在繼承體系中,子類(lèi)可以繼承到父類(lèi)的方法, 但有時(shí)子類(lèi)并不想原封不動(dòng)地繼承父類(lèi)的方法,而是想作一定的修改, 這就需要采用方法的重寫(xiě),方法重寫(xiě)又稱(chēng)方法覆蓋。
class Fu { public void catch(){ System.out.println("使用弓箭捉羊..."); } } class Zi extends Fu { @Override public void catch(){ System.out.println("使用98Kar狙擊槍捉羊..."); } } ```
注意事項(xiàng) :
> 解釋 :
>
> 父類(lèi)中私有的方法, 子類(lèi)訪問(wèn)不到, 就更不能重寫(xiě)了
>
> 子類(lèi)重寫(xiě)父類(lèi)方法, 可以理解為是對(duì)功能進(jìn)行增強(qiáng), 那如果允許把訪問(wèn)權(quán)限降低, 那豈不是功能越做越差了嘛
Java 中繼承的特點(diǎn) :
了解了 Java 中方法重寫(xiě)后, 我們?cè)賮?lái)說(shuō)下 Java 中繼承的特點(diǎn)
* 特點(diǎn) : Java只支持單繼承,不支持多繼承,但支持多層繼承
* 其實(shí)就是說(shuō), 一個(gè)類(lèi), 最多只能有一個(gè)父類(lèi), 不能同時(shí)有多個(gè)父類(lèi)
class A {} class B {} class C extends A , B {} // 編譯出錯(cuò) ```
* 為什么 ? 因?yàn)?*擔(dān)心邏輯沖突**
* 問(wèn)題 : 那多層繼承呢 ?
5. 繼承中構(gòu)造方法的訪問(wèn)特點(diǎn)
* 思考問(wèn)題 : 父類(lèi)的構(gòu)造方法, 子類(lèi)能不能繼承到呢 ?
* 回答 : 不允許, 因?yàn)闃?gòu)造方法要求, 方法名和類(lèi)名必須相同, 如果子類(lèi)繼承到父類(lèi)的構(gòu)造方法, 那方法名和類(lèi)名就不一致了。
* 結(jié)論 : 子類(lèi)不能繼承父類(lèi)的構(gòu)造方法, 將來(lái)子類(lèi)的構(gòu)造方法, 需要自己編寫(xiě)。
public Fu { public Fu(){} } class Zi extends Fu { public Fu(){} // 如果子類(lèi)繼承到父類(lèi)的構(gòu)造方法, 那方法名和類(lèi)名就不一致了 } ```
繼承中構(gòu)造方法的訪問(wèn)流程
* 閱讀代碼材料 :
public class Test { public static void main(String[] args) { Zi z1 = new Zi(); Zi z2 = new Zi(10); } } class Fu { public Fu() { System.out.println("Fu類(lèi)空參構(gòu)造方法"); } public Fu(int num) { System.out.println("Fu類(lèi)帶參數(shù)構(gòu)造方法"); } } class Zi extends Fu { public Zi() { System.out.println("Zi類(lèi)空參數(shù)構(gòu)造方法"); } public Zi(int num) { System.out.println("Zi類(lèi)帶參數(shù)構(gòu)造方法"); } } ```
* 運(yùn)行效果 :
Fu類(lèi)空參構(gòu)造方法 Zi類(lèi)空參數(shù)構(gòu)造方法 Fu類(lèi)空參構(gòu)造方法 Zi類(lèi)帶參數(shù)構(gòu)造方法 ```
通過(guò)打印效果同學(xué)們可以看出, 我們無(wú)論通過(guò)子類(lèi)的哪一個(gè)構(gòu)造方法創(chuàng)建對(duì)象, 都會(huì)執(zhí)行到 Fu類(lèi)的空參構(gòu)造方法。
為什么 ? 因?yàn)樵谧宇?lèi)的構(gòu)造方法, 第一行代碼, 系統(tǒng)會(huì)默認(rèn)幫我們加入 super ();
通過(guò)super() 在訪問(wèn)父類(lèi)的構(gòu)造方法 , 現(xiàn)在我們手動(dòng)把 super(); 加上, 同學(xué)們?cè)匍喿x下試試吧~
public class Test { public static void main(String[] args) { Zi z1 = new Zi(); Zi z2 = new Zi(10); } } class Fu { public Fu() { System.out.println("Fu類(lèi)空參構(gòu)造方法"); } public Fu(int num) { System.out.println("Fu類(lèi)帶參數(shù)構(gòu)造方法"); } } class Zi extends Fu { public Zi() { super(); System.out.println("Zi類(lèi)空參數(shù)構(gòu)造方法"); } public Zi(int num) { super(); System.out.println("Zi類(lèi)帶參數(shù)構(gòu)造方法"); } } ```
okk, 看懂了執(zhí)行流程之后, 新的問(wèn)題來(lái)了, 我們知道學(xué)習(xí)這個(gè)細(xì)節(jié), 對(duì)代碼有什么幫助呢 ?
答案是 : 子類(lèi)將來(lái)可以把一部分?jǐn)?shù)據(jù), 交給父類(lèi)初始化了
我們來(lái)看一段代碼
public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } ``` ```java public class Student extends Person { private double score; public Student() { } public Student(String name, int age, double score) { super(name, age); this.score = score; } public double getScore() { return score; } public void setScore(double score) { this.score = score; } public void study() { System.out.println( "姓名為" + super.getName() + ", 年齡為" + super.getAge() + ", 成績(jī)?yōu)?quot; + score + "分的學(xué)生, 正在學(xué)習(xí)Java"); } } ``` ```java public class ExtendsTest2 { public static void main(String[] args) { Student stu = new Student("李四", 24, 100); stu.study(); } } ```
在上述代碼中, 我們可以發(fā)現(xiàn), main方法中 , 創(chuàng)建學(xué)生對(duì)象, 我們給了 3個(gè)參數(shù), 先執(zhí)行子類(lèi)的帶參數(shù)構(gòu)造方法。
其中前兩個(gè)數(shù)據(jù), 我們通過(guò)super, 交給了父類(lèi)初始化, 最后一個(gè)成績(jī)數(shù)據(jù), 父類(lèi)沒(méi)有, 子類(lèi)自己完成了初始化。
public Student(String name, int age, double score) { // "李四", 24, 100 super(name, age); // "李四", 24 交給父類(lèi) this.score = score; // 100 子類(lèi)自己初始化 } ```
配合一張內(nèi)存圖 , 大家再看一下
這里我們可以發(fā)現(xiàn), 堆內(nèi)存的對(duì)象中, 會(huì)存在一個(gè) super 區(qū)域, 專(zhuān)門(mén)存放父類(lèi)的成員。
所以, 前兩個(gè)數(shù)據(jù), 父類(lèi)中有, 就交給父類(lèi)初始化, 最后一個(gè)數(shù)據(jù), 父類(lèi)沒(méi)有, 子類(lèi)就自己完成初始化。
6. this 和 super
上述文章中, 我們其實(shí)關(guān)于 this 和 super 做了很多使用了, 接下來(lái)我們來(lái)梳理下
this:代表本類(lèi)對(duì)象的引用
super:代表父類(lèi)存儲(chǔ)空間的標(biāo)識(shí)(可以理解為父類(lèi)對(duì)象引用)
好啦, 本次關(guān)于繼承的技術(shù), 就跟大家聊到這里了
我們下次再見(jiàn) ~
掰掰~ (*^▽^*)