安徽做政府網(wǎng)站的企業(yè)日結(jié)app推廣聯(lián)盟
?作者簡介:大家好,我是橘橙黃又青,一個想要與大家共同進步的男人😉😉
🍎個人主頁:再無B~U~G-CSDN博客
1.繼承
1.1 為什么需要繼承
Java 中使用類對現(xiàn)實世界中實體來進行描述,類經(jīng)過實例化之后的產(chǎn)物對象,則可以用來表示現(xiàn)實中的實體,但是現(xiàn)實世界錯綜復雜,事物之間可能會存在一些關聯(lián),那在設計程序是就需要考慮。
比如: 狗和貓,它們都是一個動物 。


使用 Java 語言來進行描述,就會設計出:
// Dog.java
public class Dog{string name;int age;float weight;public void eat(){System.out.println(name + "正在吃飯");}public void sleep(){System.out.println(name + "正在睡覺");}void Bark(){System.out.println(name + "汪汪汪~~~");}
}
// Cat.Java
public class Cat{string name;int age;float weight;public void eat(){System.out.println(name + "正在吃飯");}public void sleep(){System.out.println(name + "正在睡覺");}void mew(){System.out.println(name + "喵喵喵~~~");}
}
通過觀察上述代碼會發(fā)現(xiàn),貓和狗的類中存在大量重復,如下所示:

那能否將這些共性抽取呢? 面向?qū)ο笏枷胫刑岢隽死^承的概念,專門用來進行共性抽取,實現(xiàn)代碼復用 。
1.2 繼承概念
繼承 (inheritance) 機制 :是面向?qū)ο蟪绦蛟O計使代碼可以復用的最重要的手段,它允許程序員在保持原有類特 性的基礎上進行擴展,增加新功能 ,這樣產(chǎn)生新的類,稱 派生類 。繼承呈現(xiàn)了面向?qū)ο蟪绦蛟O計的層次結(jié)構(gòu), 體現(xiàn)了由簡單到復雜的認知過程。繼承主要解決的問題是:共性的抽取,實現(xiàn)代碼復用 。
例如:狗和貓都是動物,那么我們就可以將共性的內(nèi)容進行抽取,然后采用繼承的思想來達到共用。

上述圖示中, Dog 和 Cat 都繼承了 Animal 類,其中: Animal 類稱為父類 / 基類或超類, Dog 和 Cat 可以稱為 Animal 的子類/ 派生類,繼承之后,子類可以復用父類中成員,子類在實現(xiàn)時只需關心自己新增加的成員即可。
從繼承概念中可以看出繼承最大的作用就是:實現(xiàn)代碼復用,還有就是來實現(xiàn)多態(tài) ( 后序講 ) 。
1.3 繼承的語法
在 Java 中如果要表示類之間的繼承關系,需要借助 extends 關鍵字,具體如下:
格式:
修飾符 class 子類 extends 父類 {// ...}
// Animal.java
public class Animal{
String name;int age;public void eat(){System.out.println(name + "正在吃飯");}public void sleep(){System.out.println(name + "正在睡覺");}
}
// Dog.java
public class Dog extends Animal{void bark(){System.out.println(name + "汪汪汪~~~");}
}// Cat.Java
public class Cat extends Animal{void mew(){System.out.println(name + "喵喵喵~~~");}
}
// TestExtend.java
public class TestExtend {public static void main(String[] args) {Dog dog = new Dog();// dog類中并沒有定義任何成員變量,name和age屬性肯定是從父類Animal中繼承下來的System.out.println(dog.name);System.out.println(dog.age);// dog訪問的eat()和sleep()方法也是從Animal中繼承下來的dog.eat();dog.sleep();dog.bark();}
}
?注意:
1. 子類會將父類中的成員變量或者成員方法繼承到子類中了2. 子類繼承父類之后,必須要新添加自己特有的成員,體現(xiàn)出與基類的不同,否則就沒有必要繼承了
?1.4 父類成員訪問
在繼承體系中,子類將父類中的方法和字段繼承下來了,那在子類中能否直接訪問父類中繼承下來的成員呢?
1.4.1 子類中訪問父類的成員變量
1. 子類和父類不存在同名成員變量
public class Base {int a;int b;
}
public class Derived extends Base{int c;public void method(){a = 10; // 訪問從父類中繼承下來的ab = 20; // 訪問從父類中繼承下來的bc = 30; // 訪問子類自己的c}
}
2. 子類和父類成員變量同名
public class Base {int a;int b;int c;
}
/
public class Derived extends Base{int a; // 與父類中成員a同名,且類型相同char b; // 與父類中成員b同名,但類型不同public void method(){a = 100; // 訪問父類繼承的a,還是子類自己新增的a?b = 101; // 訪問父類繼承的b,還是子類自己新增的b?c = 102; // 子類沒有c,訪問的肯定是從父類繼承下來的c// d = 103; // 編譯失敗,因為父類和子類都沒有定義成員變量b}
}
在子類方法中 或者 通過子類對象訪問成員時 :
- 如果訪問的成員變量子類中有,優(yōu)先訪問自己的成員變量。
- 如果訪問的成員變量子類中無,則訪問父類繼承下來的,如果父類也沒有定義,則編譯報錯。
- 如果訪問的成員變量與父類中成員變量同名,則優(yōu)先訪問自己的。
成員變量訪問遵循就近原則,自己有優(yōu)先自己的,如果沒有則向父類中找。?
1.4.2 子類中訪問父類的成員方法
1. 成員方法名字不同
public class Base {public void methodA(){System.out.println("Base中的methodA()");}
}
public class Derived extends Base{public void methodB(){System.out.println("Derived中的methodB()方法");}public void methodC(){methodB(); // 訪問子類自己的methodB()methodA(); // 訪問父類繼承的methodA()// methodD(); // 編譯失敗,在整個繼承體系中沒有發(fā)現(xiàn)方法methodD()}
}
總結(jié):成員方法沒有同名時,在子類方法中或者通過子類對象訪問方法時,則優(yōu)先訪問自己的,自己沒有時再到父類中找,如果父類中也沒有則報錯。
2. 成員方法名字相同
public class Base {public void methodA(){System.out.println("Base中的methodA()");}public void methodB(){System.out.println("Base中的methodB()");}
}
public class Derived extends Base{public void methodA(int a) {System.out.println("Derived中的method(int)方法");}public void methodB(){System.out.println("Derived中的methodB()方法");}public void methodC(){methodA(); // 沒有傳參,訪問父類中的methodA()methodA(20); // 傳遞int參數(shù),訪問子類中的methodA(int)methodB(); // 直接訪問,則永遠訪問到的都是子類中的methodB(),基類的無法訪問到}
}
1.5 super關鍵字
public class Base {int a;int b;public void methodA(){System.out.println("Base中的methodA()");}public void methodB(){System.out.println("Base中的methodB()");}
}
public class Derived extends Base{int a; // 與父類中成員變量同名且類型相同char b; // 與父類中成員變量同名但類型不同// 與父類中methodA()構(gòu)成重載public void methodA(int a) {System.out.println("Derived中的method()方法");}// 與基類中methodB()構(gòu)成重寫(即原型一致,重寫后序詳細介紹)public void methodB(){System.out.println("Derived中的methodB()方法");}public void methodC(){// 對于同名的成員變量,直接訪問時,訪問的都是子類的a = 100; // 等價于: this.a = 100;b = 101; // 等價于: this.b = 101;// 注意:this是當前對象的引用// 訪問父類的成員變量時,需要借助super關鍵字// super是獲取到子類對象中從基類繼承下來的部分super.a = 200;super.b = 201;// 父類和子類中構(gòu)成重載的方法,直接可以通過參數(shù)列表區(qū)分清訪問父類還是子類方法methodA(); // 沒有傳參,訪問父類中的methodA()methodA(20); // 傳遞int參數(shù),訪問子類中的methodA(int)// 如果在子類中要訪問重寫的基類方法,則需要借助super關鍵字methodB(); // 直接訪問,則永遠訪問到的都是子類中的methodA(),基類的無法訪問到super.methodB(); // 訪問基類的methodB()}
}
在子類方法中,如果想要明確訪問父類中成員時,借助 super 關鍵字即可。
【 注意事項 】
1. 只能在非靜態(tài)方法中使用2. 在子類方法中,訪問父類的成員變量和方法 。
?super的其他用法在后文中介紹。
1.6 子類構(gòu)造方法
父子父子,先有父再有子,即:子類對象構(gòu)造時,需要先調(diào)用基類構(gòu)造方法,然后執(zhí)行子類的構(gòu)造方法。
public class Base {public Base(){System.out.println("Base()");}
}
public class Derived extends Base{public Derived(){// super(); // 注意子類構(gòu)造方法中默認會調(diào)用基類的無參構(gòu)造方法:super(),// 用戶沒有寫時,編譯器會自動添加,而且super()必須是子類構(gòu)造方法中第一條語句,// 并且只能出現(xiàn)一次System.out.println("Derived()");}
}
public class Test {public static void main(String[] args) {Derived d = new Derived();}
}
//結(jié)果打印:
//Base()
//Derived()
在子類構(gòu)造方法中,并沒有寫任何關于基類構(gòu)造的代碼,但是在構(gòu)造子類對象時,先執(zhí)行基類的構(gòu)造方法,然后執(zhí)行子類的構(gòu)造方法,因為:子類對象中成員是有兩部分組成的,基類繼承下來的以及子類新增加的部分 。父子父子 肯定是先有父再有子,所以在構(gòu)造子類對象時候 ,先要調(diào)用基類的構(gòu)造方法,將從基類繼承下來的成員構(gòu)造完整 ,然后再調(diào)用子類自己的構(gòu)造方法,將子類自己新增加的成員初始化完整 。
注意:
1. 若父類顯式定義無參或者默認的構(gòu)造方法,在子類構(gòu)造方法第一行默認有隱含的super()調(diào)用,即調(diào)用基類構(gòu) 造方法2. 如果父類構(gòu)造方法是帶有參數(shù)的,此時需要用戶為子類顯式定義構(gòu)造方法,并在子類構(gòu)造方法中選擇合適的父類構(gòu)造方法調(diào)用,否則編譯失敗。3. 在子類構(gòu)造方法中,super(...)調(diào)用父類構(gòu)造時,必須是子類構(gòu)造函數(shù)中第一條語句。4. super(...)只能在子類構(gòu)造方法中出現(xiàn)一次,并且不能和this同時出現(xiàn)
?1.7 super和this
super 和 this 都可以在成員方法中用來訪問:成員變量和調(diào)用其他的成員函數(shù),都可以作為構(gòu)造方法的第一條語句,那他們之間有什么區(qū)別呢?
【 相同點 】
1. 都是Java中的關鍵字2. 只能在類的非靜態(tài)方法中使用,用來訪問非靜態(tài)成員方法和字段3. 在構(gòu)造方法中調(diào)用時,必須是構(gòu)造方法中的第一條語句,并且不能同時存在
【不同點】 ?
1. this 是當前對象的引用,當前對象即調(diào)用實例方法的對象, super 相當于是子類對象中從父類繼承下來部分成員的引用。
直觀表示:

2. 在 非靜態(tài)成員方法 中, this 用來訪問本類的方法和屬性, super 用來訪問父類繼承下來的方法和屬性。
3. 在構(gòu)造方法中: this(...) 用于調(diào)用本類構(gòu)造方法, super(...) 用于調(diào)用父類構(gòu)造方法,兩種調(diào)用不能同時在構(gòu)造方法中出現(xiàn)。
4. 構(gòu)造方法中一定會存在 super(...) 的調(diào)用,用戶沒有寫編譯器也會增加,但是 this(...) 用戶不寫則沒有。
?
1.8 再談初始化
我們還記得之前講過的代碼塊嗎?那么繼承關系時的執(zhí)行順序是怎么樣的呢?
1. 靜態(tài)代碼塊先執(zhí)行,并且只執(zhí)行一次,在類加載階段執(zhí)行2. 當有對象創(chuàng)建時,才會執(zhí)行實例代碼塊,實例代碼塊執(zhí)行完成后,最后構(gòu)造方法執(zhí)行
【繼承關系上的執(zhí)行順序】
案例:
?
class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;System.out.println("Person:構(gòu)造方法執(zhí)行");}//代碼實例化{System.out.println("Person:實例代碼塊執(zhí)行");}//靜態(tài)實例化static {System.out.println("Person:靜態(tài)代碼塊執(zhí)行");}
}
class Student extends Person{public Student(String name,int age) {super(name,age);System.out.println("Student:構(gòu)造方法執(zhí)行");}{System.out.println("Student:實例代碼塊執(zhí)行");}static {System.out.println("Student:靜態(tài)代碼塊執(zhí)行");}
}
public class TestDemo4 {public static void main(String[] args) {Student student1 = new Student("張三",19);System.out.println("===========================");Student student2 = new Student("gaobo",20);}public static void main1(String[] args) {Person person1 = new Person("bit",10);System.out.println("============================");Person person2 = new Person("gaobo",20);}
}
輸出結(jié)果:
總結(jié):
1、父類靜態(tài)代碼塊優(yōu)先于子類靜態(tài)代碼塊執(zhí)行,且是最早執(zhí)行2、父類實例代碼塊和父類構(gòu)造方法緊接著執(zhí)行3、子類的實例代碼塊和子類構(gòu)造方法緊接著再執(zhí)行4、第二次實例化子類對象時,父類和子類的靜態(tài)代碼塊都將不會再執(zhí)行
?
1.9 protected 關鍵字
在類和對象章節(jié)中,為了實現(xiàn)封裝特性, Java 中引入了訪問限定符,主要限定:類或者類中成員能否在類外或者其 他包中被訪問。

重點:
// extend01包中
public class B {private int a;protected int b;public int c;int d;
}// extend01包中
// 同一個包中的子類
public class D extends B{public void method(){// super.a = 10; // 編譯報錯,父類private成員在相同包子類中不可見super.b = 20; // 父類中protected成員在相同包子類中可以直接訪問super.c = 30; // 父類中public成員在相同包子類中可以直接訪問super.d = 40; // 父類中默認訪問權(quán)限修飾的成員在相同包子類中可以直接訪問}
}// extend02包中
// 不同包中的子類
public class C extends B {public void method(){// super.a = 10; // 編譯報錯,父類中private成員在不同包子類中不可見super.b = 20; // 父類中protected修飾的成員在不同包子類中可以直接訪問super.c = 30; // 父類中public修飾的成員在不同包子類中可以直接訪問//super.d = 40; // 父類中默認訪問權(quán)限修飾的成員在不同包子類中不能直接訪問}
}// extend02包中
// 不同包中的類
public class TestC {public static void main(String[] args) {C c = new C();c.method();// System.out.println(c.a); // 編譯報錯,父類中private成員在不同包其他類中不可見// System.out.println(c.b); // 父類中protected成員在不同包其他類中不能直接訪問System.out.println(c.c); // 父類中public成員在不同包其他類中可以直接訪問// System.out.println(c.d); // 父類中默認訪問權(quán)限修飾的成員在不同包其他類中不能直接訪問}
}
注意:父類中 private 成員變量雖然在子類中不能直接訪問,但是也繼承到子類中了
什么時候下用哪一種呢?
- 我們希望類要盡量做到 "封裝", 即隱藏內(nèi)部實現(xiàn)細節(jié), 只暴露出 必要 的信息給類的調(diào)用者。
- 因此我們在使用的時候應該盡可能的使用 比較嚴格 的訪問權(quán)限. 例如如果一個方法能用 private, 就盡量不要用 public.
- 另外, 還有一種 簡單粗暴 的做法: 將所有的字段設為 private, 將所有的方法設為 public. 不過這種方式屬于是 對訪問權(quán)限的濫用, 還是更希望同學們能寫代碼的時候認真思考。
?1.10 繼承方式
在現(xiàn)實生活中,事物之間的關系是非常復雜,靈活多樣,比如:
但在 Java 中只支持以下幾種繼承方式:
注意: Java 中不支持多繼承 。
- 時刻牢記, 我們寫的類是現(xiàn)實事物的抽象. 而我們真正在公司中所遇到的項目往往業(yè)務比較復雜, 可能會涉及到一系列復雜的概念, 都需要我們使用代碼來表示, 所以我們真實項目中所寫的類也會有很多. 類之間的關系也會更加復雜.
- 但是即使如此, 我們并不希望類之間的繼承層次太復雜. 一般我們不希望出現(xiàn)超過三層的繼承關系. 如果繼承層
- 次太多, 就需要考慮對代碼進行重構(gòu)了.
如果想從語法上進行限制繼承 , 就可以使用 final 關鍵字
1.11 final 關鍵字?
final 關鍵可以用來修飾變量、成員方法以及類。
1. 修飾變量或字段,表示常量 ( 即不能修改 )
final int a = 10; a = 20; // 編譯出錯 String arr = " "; arr = 20; // 編譯出錯
?
2. 修飾類:表示此類不能被繼承?
final public class Animal {...}public class Bird extends Animal {...}// 編譯出錯Error :( 3 , 27 ) java : 無法從最終 com . bit . Animal 進行繼
3. 修飾方法:表示該方法不能被重寫 ( 后序介紹 )
1.12 繼承與組合
和繼承類似, 組合也是一種表達類之間關系的方式, 也是能夠達到代碼重用的效果。組合并沒有涉及到特殊的語法 (諸如 extends 這樣的關鍵字), 僅僅是將一個類的實例作為另外一個類的字段。
繼承表示對象之間是 is-a 的關系 ,比如:狗是動物,貓是動物
組合表示對象之間是 has-a 的關系 ,比如:汽車


汽車和其輪胎、發(fā)動機、方向盤、車載系統(tǒng)等的關系就應該是組合,因為汽車是有這些部件組成的。
案例代碼:
// 輪胎類
class Tire{// ...
}
// 發(fā)動機類
class Engine{// ...
}// 車載系統(tǒng)類
class VehicleSystem{// ...
}
class Car{private Tire tire; // 可以復用輪胎中的屬性和方法private Engine engine; // 可以復用發(fā)動機中的屬性和方法private VehicleSystem vs; // 可以復用車載系統(tǒng)中的屬性和方法// ...
}
// 奔馳是汽車
class Benz extend Car{// 將汽車中包含的:輪胎、發(fā)送機、車載系統(tǒng)全部繼承下來
}
組合和繼承都可以實現(xiàn)代碼復用,應該使用繼承還是組合,需要根據(jù)應用場景來選擇,一般建議:能用組合盡量用組合。
今天就到這里了,感謝觀看。