一文詳細(xì)講解Java的父子繼承、方法的重寫與super關(guān)鍵字
一、java的繼承
1.舉例

多個類中存在相同屬性和行為時,將這些內(nèi)容抽取到單獨一個類中,那么多個類中無需再定義這些屬性和行為,只需要和抽取出來的類構(gòu)成繼承關(guān)系。如圖所示:

再舉例:

2. 繼承的好處
繼承的出現(xiàn)減少了代碼冗余,提高了代碼的復(fù)用性。
繼承的出現(xiàn),更有利于功能的擴展。
繼承的出現(xiàn)讓類與類之間產(chǎn)生了
is-a的關(guān)系,為多態(tài)的使用提供了前提。繼承描述事物之間的所屬關(guān)系,這種關(guān)系是:
is-a的關(guān)系。可見,父類更通用、更一般,子類更具體。
注意:不要僅為了獲取其他類中某個功能而去繼承!
3. 繼承的語法
3.1 繼承中的語法格式
通過 extends 關(guān)鍵字,可以聲明一個類B繼承另外一個類A,定義格式如下:
[修飾符] class 類A {
...
}
[修飾符] class 類B extends 類A {
...
}3.2 繼承中的基本概念
類B,稱為子類、派生類(derived class)、SubClass
類A,稱為父類、超類、基類(base class)、SuperClass
3.3 代碼舉例
1、父類
package com.atguigu.inherited.grammar;
/*
* 定義動物類Animal,做為父類
*/
public class Animal {
// 定義name屬性
String name;
// 定義age屬性
int age;
// 定義動物的吃東西方法
public void eat() {
System.out.println(age + "歲的"
+ name + "在吃東西");
}
}2、子類
package com.atguigu.inherited.grammar;
/*
* 定義貓類Cat 繼承 動物類Animal
*/
public class Cat extends Animal {
int count;//記錄每只貓抓的老鼠數(shù)量
// 定義一個貓抓老鼠的方法catchMouse
public void catchMouse() {
count++;
System.out.println("抓老鼠,已經(jīng)抓了"
+ count + "只老鼠");
}
}3、測試類
package com.atguigu.inherited.grammar;
public class TestCat {
public static void main(String[] args) {
// 創(chuàng)建一個貓類對象
Cat cat = new Cat();
// 為該貓類對象的name屬性進行賦值
cat.name = "Tom";
// 為該貓類對象的age屬性進行賦值
cat.age = 2;
// 調(diào)用該貓繼承來的eat()方法
cat.eat();
// 調(diào)用該貓的catchMouse()方法
cat.catchMouse();
cat.catchMouse();
cat.catchMouse();
}
}3.4 繼承性的細(xì)節(jié)說明
1、子類會繼承父類所有的實例變量和實例方法
從類的定義來看,類是一類具有相同特性的事物的抽象描述。父類是所有子類共同特征的抽象描述。而實例變量和實例方法就是事物的特征,那么父類中聲明的實例變量和實例方法代表子類事物也有這個特征。
當(dāng)子類對象被創(chuàng)建時,在堆中給對象申請內(nèi)存時,就要看子類和父類都聲明了什么實例變量,這些實例變量都要分配內(nèi)存。
當(dāng)子類對象調(diào)用方法時,編譯器會先在子類模板中看該類是否有這個方法,如果沒找到,會看它的父類甚至父類的父類是否聲明了這個方法,遵循從下往上找的順序,找到了就停止,一直到根父類都沒有找到,就會報編譯錯誤。
所以繼承意味著子類的對象除了看子類的類模板還要看父類的類模板。

2、子類不能直接訪問父類中私有的(private)的成員變量和方法
子類雖會繼承父類私有(private)的成員變量,但子類不能對繼承的私有成員變量直接進行訪問,可通過繼承的get/set方法進行訪問。如圖所示:

3、在Java 中,繼承的關(guān)鍵字用的是“extends”,即子類不是父類的子集,而是對父類的“擴展”
子類在繼承父類以后,還可以定義自己特有的方法,這就可以看做是對父類功能上的擴展。
4、Java支持多層繼承(繼承體系)

class A{}
class B extends A{}
class C extends B{}
說明:
子類和父類是一種相對的概念
頂層父類是Object類。所有的類默認(rèn)繼承Object,作為父類。
5、一個父類可以同時擁有多個子類*
class A{}
class B extends A{}
class D extends A{}
class E extends A{}6、Java只支持單繼承,不支持多重繼承

public class A{}
class B extends A{}
//一個類只能有一個父類,不可以有多個直接父類。
class C extends B{} //ok
class C extends A,B... //error二、方法的重寫(override/overwrite)
父類的所有方法子類都會繼承,但是當(dāng)某個方法被繼承到子類之后,子類覺得父類原來的實現(xiàn)不適合于自己當(dāng)前的類,該怎么辦呢?子類可以對從父類中繼承來的方法進行改造,我們稱為方法的重寫 (override、overwrite)。也稱為方法的重置、覆蓋。
在程序執(zhí)行時,子類的方法將覆蓋父類的方法。
2.1 方法重寫舉例
比如新的手機增加來電顯示頭像的功能,代碼如下:
package com.atguigu.inherited.method;
public class Phone {
public void sendMessage(){
System.out.println("發(fā)短信");
}
public void call(){
System.out.println("打電話");
}
public void showNum(){
System.out.println("來電顯示號碼");
}
}package com.atguigu.inherited.method;
//SmartPhone:智能手機
public class SmartPhone extends Phone{
//重寫父類的來電顯示功能的方法
@Override
public void showNum(){
//來電顯示姓名和圖片功能
System.out.println("顯示來電姓名");
System.out.println("顯示頭像");
}
//重寫父類的通話功能的方法
@Override
public void call() {
System.out.println("語音通話 或 視頻通話");
}
}package com.atguigu.inherited.method;
public class TestOverride {
public static void main(String[] args) {
// 創(chuàng)建子類對象
SmartPhone sp = new SmartPhone();
// 調(diào)用父類繼承而來的方法
sp.call();
// 調(diào)用子類重寫的方法
sp.showNum();
}
}@Override使用說明:
寫在方法上面,用來檢測是不是滿足重寫方法的要求。這個注解就算不寫,只要滿足要求,也是正確的方法覆蓋重寫。建議保留,這樣編譯器可以幫助我們檢查格式,另外也可以讓閱讀源代碼的程序員清晰的知道這是一個重寫的方法。
2.2 方法重寫的要求
子類重寫的方法必須和父類被重寫的方法具有相同的方法名稱、參數(shù)列表。
子類重寫的方法的返回值類型不能大于父類被重寫的方法的返回值類型。(例如:Student < Person)。
注意:如果返回值類型是基本數(shù)據(jù)類型和void,那么必須是相同
3. 子類重寫的方法使用的訪問權(quán)限不能小于父類被重寫的方法的訪問權(quán)限。(public > protected > 缺省 > private)
注意:① 父類私有方法不能重寫 ② 跨包的父類缺省的方法也不能重寫
4.子類方法拋出的異常不能大于父類被重寫方法的異常
此外,子類與父類中同名同參數(shù)的方法必須同時聲明為非static的(即為重寫),或者同時聲明為static的(不是重寫)。因為static方法是屬于類的,子類無法覆蓋父類的方法。
2.3 小結(jié):方法的重載與重寫區(qū)別
方法的重載:方法名相同,形參列表不同。不看返回值類型。
方法的重寫:見上面。
(1)同一個類中的方法只會存在重載
package com.atguigu.inherited.method;
public class TestOverload {
public int max(int a, int b){
return a > b ? a : b;
}
public double max(double a, double b){
return a > b ? a : b;
}
public int max(int a, int b,int c){
return max(max(a,b),c);
}
}(2)父子類中,即可能有重載也可以有重寫
package com.atguigu.inherited.method;
public class TestOverloadOverride {
public static void main(String[] args) {
Son s = new Son();
s.method(1);//只有一個形式的method方法
Daughter d = new Daughter();
d.method(1);
d.method(1,2);//有兩個形式的method方法
}
}
class Father{
public void method(int i){
System.out.println("Father.method");
}
}
class Son extends Father{
public void method(int i){//重寫
System.out.println("Son.method");
}
}
class Daughter extends Father{
public void method(int i,int j){//重載
System.out.println("Daughter.method");
}
}三、關(guān)鍵字:super
3.1 super的理解
在Java類中使用super來調(diào)用父類中的指定操作:
super可用于訪問父類中定義的屬性
super可用于調(diào)用父類中定義的成員方法
super可用于在子類構(gòu)造器中調(diào)用父類的構(gòu)造器
注意:
尤其當(dāng)子父類出現(xiàn)同名成員時,可以用super表明調(diào)用的是父類中的成員
super的追溯不僅限于直接父類
super和this的用法相像,this代表本類對象的引用,super代表父類的內(nèi)存空間的標(biāo)識
3.2 super的使用場景
3.2.1 子類中調(diào)用父類被重寫的方法
如果子類沒有重寫父類的方法,只要權(quán)限修飾符允許,在子類中完全可以直接調(diào)用父類的方法;
如果子類重寫了父類的方法,在子類中需要通過
super.才能調(diào)用父類被重寫的方法,否則默認(rèn)調(diào)用的子類重寫的方法
舉例:
package com.atguigu.inherited.method;
public class Phone {
public void sendMessage(){
System.out.println("發(fā)短信");
}
public void call(){
System.out.println("打電話");
}
public void showNum(){
System.out.println("來電顯示號碼");
}
}
//smartphone:智能手機
public class SmartPhone extends Phone{
//重寫父類的來電顯示功能的方法
public void showNum(){
//來電顯示姓名和圖片功能
System.out.println("顯示來電姓名");
System.out.println("顯示頭像");
//保留父類來電顯示號碼的功能
super.showNum();//此處必須加super.,否則就是無限遞歸,那么就會棧內(nèi)存溢出
}
}總結(jié):
方法前面沒有super.和this.
先從子類找匹配方法,如果沒有,再從直接父類找,再沒有,繼續(xù)往上追溯
方法前面有this.
先從子類找匹配方法,如果沒有,再從直接父類找,再沒有,繼續(xù)往上追溯
方法前面有super.
從當(dāng)前子類的直接父類找,如果沒有,繼續(xù)往上追溯
3.2.2 子類中調(diào)用父類中同名的成員變量
如果實例變量與局部變量重名,可以在實例變量前面加this.進行區(qū)別
如果子類實例變量和父類實例變量重名,并且父類的該實例變量在子類仍然可見,在子類中要訪問父類聲明的實例變量需要在父類實例變量前加super.,否則默認(rèn)訪問的是子類自己聲明的實例變量
如果父子類實例變量沒有重名,只要權(quán)限修飾符允許,在子類中完全可以直接訪問父類中聲明的實例變量,也可以用this.實例訪問,也可以用super.實例變量訪問
舉例:
class Father{
int a = 10;
int b = 11;
}
class Son extends Father{
int a = 20;
public void test(){
//子類與父類的屬性同名,子類對象中就有兩個a
System.out.println("子類的a:" + a);//20 先找局部變量找,沒有再從本類成員變量找
System.out.println("子類的a:" + this.a);//20 先從本類成員變量找
System.out.println("父類的a:" + super.a);//10 直接從父類成員變量找
//子類與父類的屬性不同名,是同一個b
System.out.println("b = " + b);//11 先找局部變量找,沒有再從本類成員變量找,沒有再從父類找
System.out.println("b = " + this.b);//11 先從本類成員變量找,沒有再從父類找
System.out.println("b = " + super.b);//11 直接從父類局部變量找
}
public void method(int a, int b){
//子類與父類的屬性同名,子類對象中就有兩個成員變量a,此時方法中還有一個局部變量a
System.out.println("局部變量的a:" + a);//30 先找局部變量
System.out.println("子類的a:" + this.a);//20 先從本類成員變量找
System.out.println("父類的a:" + super.a);//10 直接從父類成員變量找
System.out.println("b = " + b);//13 先找局部變量
System.out.println("b = " + this.b);//11 先從本類成員變量找
System.out.println("b = " + super.b);//11 直接從父類局部變量找
}
}
class Test{
public static void main(String[] args){
Son son = new Son();
son.test();
son.method(30,13);
}
}總結(jié):起點不同(就近原則)
變量前面沒有super.和this.
在構(gòu)造器、代碼塊、方法中如果出現(xiàn)使用某個變量,先查看是否是當(dāng)前塊聲明的
局部變量,如果不是局部變量,先從當(dāng)前執(zhí)行代碼的
本類去找成員變量如果從當(dāng)前執(zhí)行代碼的本類中沒有找到,會往上找
父類聲明的成員變量(權(quán)限修飾符允許在子類中訪問的)
變量前面有this.
通過this找成員變量時,先從當(dāng)前執(zhí)行代碼的==本類去找成員變量==
如果從當(dāng)前執(zhí)行代碼的本類中沒有找到,會往上找==父類聲明的成員變量(==權(quán)限修飾符允許在子類中訪問的)
變量前面super.
通過super找成員變量,直接從當(dāng)前執(zhí)行代碼的直接父類去找成員變量(權(quán)限修飾符允許在子類中訪問的)
如果直接父類沒有,就去父類的父類中找(權(quán)限修飾符允許在子類中訪問的)
特別說明:應(yīng)該避免子類聲明和父類重名的成員變量
在阿里的開發(fā)規(guī)范等文檔中都做出明確說明:

3.2.3 子類構(gòu)造器中調(diào)用父類構(gòu)造器
① 子類繼承父類時,不會繼承父類的構(gòu)造器。只能通過“super(形參列表)”的方式調(diào)用父類指定的構(gòu)造器。
② 規(guī)定:“super(形參列表)”,必須聲明在構(gòu)造器的首行。
③ 我們前面講過,在構(gòu)造器的首行可以使用"this(形參列表)",調(diào)用本類中重載的構(gòu)造器, 結(jié)合②,結(jié)論:在構(gòu)造器的首行,"this(形參列表)" 和 "super(形參列表)"只能二選一。
④ 如果在子類構(gòu)造器的首行既沒有顯示調(diào)用"this(形參列表)",也沒有顯式調(diào)用"super(形參列表)",? 則子類此構(gòu)造器默認(rèn)調(diào)用"super()",即調(diào)用父類中空參的構(gòu)造器。
⑤ 由③和④得到結(jié)論:子類的任何一個構(gòu)造器中,要么會調(diào)用本類中重載的構(gòu)造器,要么會調(diào)用父類的構(gòu)造器。 只能是這兩種情況之一。
⑥ 由⑤得到:一個類中聲明有n個構(gòu)造器,最多有n-1個構(gòu)造器中使用了"this(形參列表)",則剩下的那個一定使用"super(形參列表)"。
開發(fā)中常見錯誤:
如果子類構(gòu)造器中既未顯式調(diào)用父類或本類的構(gòu)造器,且父類中又沒有空參的構(gòu)造器,則編譯出錯
情景舉例1:
class A{
A(){
System.out.println("A類無參構(gòu)造器");
}
}
class B extends A{
}
class Test{
public static void main(String[] args){
B b = new B();
//A類顯示聲明一個無參構(gòu)造,
//B類默認(rèn)有一個無參構(gòu)造,
//B類的默認(rèn)無參構(gòu)造中會默認(rèn)調(diào)用A類的無參構(gòu)造
//可以看到會輸出“A類無參構(gòu)造器"
}
}情景舉例2:
class A{
A(){
System.out.println("A類無參構(gòu)造器");
}
}
class B extends A{
B(){
super();
System.out.println("B類無參構(gòu)造器");
}
}
class Test{
public static void main(String[] args){
B b = new B();
//A類顯示聲明一個無參構(gòu)造,
//B類顯示聲明一個無參構(gòu)造,
//B類的無參構(gòu)造中明確寫了super(),表示調(diào)用A類的無參構(gòu)造
//可以看到會輸出“A類無參構(gòu)造器"和"B類無參構(gòu)造器")
}
}情景舉例3:
class A{
A(int a){
System.out.println("A類有參構(gòu)造器");
}
}
class B extends A{
B(){
System.out.println("B類無參構(gòu)造器");
}
}
class Test05{
public static void main(String[] args){
B b = new B();
//A類顯示聲明一個有參構(gòu)造,沒有寫無參構(gòu)造,那么A類就沒有無參構(gòu)造了
//B類顯示聲明一個無參構(gòu)造,
//B類的無參構(gòu)造沒有寫super(...),表示默認(rèn)調(diào)用A類的無參構(gòu)造
//編譯報錯,因為A類沒有無參構(gòu)造
}
}情景舉例4:
class A{
A(int a){
System.out.println("A類有參構(gòu)造器");
}
}
class B extends A{
B(int a){
super(a);
System.out.println("B類有參構(gòu)造器");
}
}
class Test07{
public static void main(String[] args){
B b = new B(10);
//A類顯示聲明一個有參構(gòu)造,沒有寫無參構(gòu)造,那么A類就沒有無參構(gòu)造了
//B類顯示聲明一個有參構(gòu)造,
//B類的有參構(gòu)造明確寫super(a),表示調(diào)用A類的有參構(gòu)造
//會打印“A類有參構(gòu)造器"和"B類有參構(gòu)造器"
}
}3.3 小結(jié):this與super區(qū)別
1、this和super的意義
this:當(dāng)前對象
在構(gòu)造器和非靜態(tài)代碼塊中,表示正在new的對象
在實例方法中,表示調(diào)用當(dāng)前方法的對象
super:引用父類聲明的成員
2、this和super的使用格式
this
this.成員變量:表示當(dāng)前對象的某個成員變量,而不是局部變量
this.成員方法:表示當(dāng)前對象的某個成員方法,完全可以省略this.
this()或this(實參列表):調(diào)用另一個構(gòu)造器協(xié)助當(dāng)前對象的實例化,只能在構(gòu)造器首行,只會找本類的構(gòu)造器,找不到就報錯
super
super.成員變量:表示當(dāng)前對象的某個成員變量,該成員變量在父類中聲明的
super.成員方法:表示當(dāng)前對象的某個成員方法,該成員方法在父類中聲明的
super()或super(實參列表):調(diào)用父類的構(gòu)造器協(xié)助當(dāng)前對象的實例化,只能在構(gòu)造器首行,只會找直接父類的對應(yīng)構(gòu)造器,找不到就報錯
總結(jié)
到此這篇關(guān)于Java父子繼承、方法的重寫與super關(guān)鍵字的文章就介紹到這了,更多相關(guān)Java父子繼承、方法的重寫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot運行時新增/更新外部接口的實現(xiàn)方法
這篇文章主要介紹了springboot運行時新增/更新外部接口的實現(xiàn)方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
Spring Boot 集成 ElasticSearch應(yīng)用小結(jié)
這篇文章主要介紹了Spring Boot 集成 ElasticSearch應(yīng)用小結(jié),本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-11-11
SpringBoot使用JavaMailSender實現(xiàn)發(fā)送郵件+Excel附件
項目審批完畢后,需要發(fā)送郵件通知相關(guān)人員,并且要附帶數(shù)據(jù)庫表生成的Excel表格,這就要求不光是郵件發(fā)送功能,還要臨時生成Excel表格做為附件,本文詳細(xì)介紹了SpringBoot如何使用JavaMailSender實現(xiàn)發(fā)送郵件+Excel附件,需要的朋友可以參考下2023-10-10
java利用mybatis攔截器統(tǒng)計sql執(zhí)行時間示例
這篇文章主要介紹了java利用mybatis攔截器統(tǒng)計sql執(zhí)行時間示例,該攔截器攔截mybatis的query和update操作,能統(tǒng)計sql執(zhí)行時間2014-03-03

