面向對象的三大特徵:封裝性、繼承性、多態性。
繼承是多態的前提,如果沒有繼承,就沒有多態。
繼承是什麼
- 這裏說一個師傅和徒弟的例子。師傅把自己所有會的技藝都教給了徒弟,那從徒弟的角度來看,我們就可以說徒弟繼承了師傅的技藝。在面向對象中,師傅叫做父類、基類、超類,徒弟就叫做子類、派生類。這裏父類和子類,並不能對應於現實生活中的兒子繼承家產的例子。兒子繼承家產,兒子拿了錢,父親就沒錢了。在面向對象中,繼承強調的是一種行爲和屬性的複製。如果硬要舉父子的例子,你可以說父親不禿頂,兒子也不禿頂,繼承了他優秀的基因。父親能運動喫飯,兒子也能運動喫飯,這個例子就比較合適。
繼承解決了什麼問題
- 還是說上面那個師傅和徒弟的例子。一方面,師傅一個人學習了技藝,他就可以把這種能力傳授給很多徒弟。表現在面向對象中,就是父類完成了共性抽取,子類通過繼承父類就可以獲得這種共性。另一方面,我們知道徒弟不僅僅獲得了師傅的技藝,他還有可能擁有自己獨特的技藝。注意,這裏獨特的技藝並不是直接通過其它師傅得到的,java 中類與類之間只允許單繼承,不允許有多個直接父類,也就是說,徒弟不能直接拜多個師傅。但是可以間接有多個師傅,比如
徒弟->師傅b->師傅a
,徒弟繼承師傅a,師傅a繼承師傅b。再回到徒弟獨特的技藝上說,子類通過繼承父類,不僅僅擁有了父類的成員變量和成員方法,還可以擁有自己獨特的成員變量和方法,這樣就完成了對父類的擴展。
繼承的格式
public class 子類名 extends 父類名稱 {//類內容}
public class Fu {
int numFu;//父類成員變量
public void methodFu() {
System.out.println("這裏是父類成員方法");
}
}
public class Zi extends Fu {
}
public class Demo {
public static void main(String[] args) {
Zi zi = new Zi();
zi.numFu = 1;
zi.methodFu();
}
}
- 在上面的 demo 中我們可以看到,子類繼承父類後就可以訪問 父類的成員變量和成員方法。
繼承中成員方法的訪問特點
public class Fu {
public void method() {//父子都有
System.out.println("這裏是父類method方法");
}
public void methodFu() {//父類獨有,被繼承後子類也會擁有
System.out.println("這裏是父類methodFu方法");
}
}
public class Zi extends Fu {
public void method() {//父子都有
System.out.println("這裏是子類method方法");
}
public void methodZi() {//子類獨有
System.out.println("這裏是子類methodZi方法");
}
}
- 上面的代碼展示了成員方法存在的三種情況。
訪問規則: 創建的對象是誰,就優先用誰中的方法。沒有則向上找。
- 父類和子類有重名的方法。(這裏涉及到一個重寫(Override)的概念,下面會講)
Fu fu = new Fu();
fu.method();//輸出:"這裏是父類method方法"
Zi zi = new Zi();
zi.method();//輸出:"這裏是子類method方法"。優先用Zi類中的method方法
- 父類擁有獨有的方法,此時子類繼承後也會擁有。
Fu fu = new Fu();
fu.methodFu();//輸出:"這裏是父類methodFu方法"。父親調用自己的成員方法,當然可以。
Zi zi = new Zi();
zi.methodFu();//輸出:"這裏是父類methodFu方法" 。Zi類中沒有methodFu方法,則向上找。
- 子類擁有獨有的方法。
Fu fu = new Fu();
fu.methodZi();//錯誤,父類不會向下找子類中的方法
Zi zi = new Zi();
zi.methodZi();//輸出:"這裏是子類methodZi方法"
繼承中成員變量的訪問特點
- 直接通過 對象名.成員變量名 訪問。
規則: new的時候等號左邊是誰,就優先去誰中找相應的成員變量。沒有則向父類找。
public class Fu {
int num;
int numFu;
}
public class Zi extends Fu {
int num;
}
public class Demo {
public static void main(String[] args) {
Fu fu = new Fu();
fu.num;//訪問父類的num
Zi zi = new Zi();
zi.num;//訪問子類的num
zi.numFu;//先去Zi類中找numFu,沒有則向上找父類中的numFu。
}
}
2. 通過成員方法訪問成員變量
規則: 方法屬於誰,就優先去誰中找相應的成員變量。沒有則向父類找。
public class Fu {
int num = 10;
public int methodFu(){
return num;
}
}
public class Zi extends Fu {
int num = 20;
public int methodZi(){
return num;
}
}
public class Demo {
public static void main(String[] args) {
Zi fu = new Zi();
zi.methodZi();//返回20,methodZi屬於Zi類,所以會輸出Zi中的num
zi.methodFu();//返回10,methodFu屬於Fu類,所以會輸出Fu中的num
}
}
方法重寫
- 如果子類對父類繼承過來的方法不滿意,想要增加邏輯或者自己編寫邏輯,就需要重寫。
public class Fu {
public void method() {
System.out.println("這是父類method方法");
}
}
public class Zi extends Fu {
@Override//這個註解可以幫助我們檢查父類是否有這個方法,同時表明這是一個覆寫的方法。
public void method() {
System.out.println("這是子類method方法");
}
}
public class Demo {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method();//輸出:"這是子類method方法"
}
}
- 重寫和重載的區別:
- 重寫(Override):方法的名稱一樣,參數列表也一樣。
- 重載(Overload):方法的名稱一樣,參數列表不一樣。
- 方法重寫的注意點:
- 子類必須保證重寫的方法的名稱和參數列表與父類的相同。
- 子類重寫的方法的返回值必須小於等於父類方法的返回值範圍。
public class Fu {
public Object method() {
return null;
}
}
public class Zi extends Fu {
@Override
public String method() {//這樣寫是可以的,如果父類是返回String,這裏返回Object就不行了。
return null;
}
}
- 子類重寫的方法的權限必須大於等於父類方法的權限修飾符。不過當父類方法的權限修飾符爲 private 時,無法被重寫。
權限修飾符 :public > protected > default(代表什麼都不寫) > private
public class Fu {
String method() {
return null;
}
}
public class Zi extends Fu {
@Override
public String method() {//這裏權限修飾符不寫,寫protected,public都行
return null;
}
}
this 和 super 關鍵字
- this 代表本類對象,super 代表父類對象。
- 從成員變量的使用上來說:
public class Fu {
int num;
}
public class Zi extends Fu {
int num;
private void method(int num) {
System.out.println(num);//輸出method方法上的num參數
System.out.println(this.num);//輸出Zi類的成員變量num
System.out.println(super.num);//輸出Fu類的成員變量num
}
}
- 從成員方法的使用來說:
public class Fu {
public void method() {}
}
public class Zi extends Fu {
public void method() {
super.method();//調用父類的method方法
this.methodZi();//調用本類的methodZi方法
}
public void methodZi() {}
}
- 從構造方法上來說:
public class Fu {
//父類無參構造
public Fu() { }
}
public class Zi extends Fu {
public Zi() {
super();//調用父類的無參構造
}
public Zi(int num) {
this(num,null);//調用下面的構造方法
}
public Zi(int num,Object obj) {
}
}
- this 和 super 關鍵字內存圖解
講到super關鍵字,在這就可以補充一個小知識點:爲什麼 java 類與類只允許單繼承?假設一個類繼承了兩個父類,父類a有一個成員變量obj,父類b也有一個成員變量obj。那麼在子類中調用super.obj
的時候就會出問題,我調用的到底是父類a的obj還是父類b的obj那?很明顯,我們不清楚。所以java不允許多繼承。
繼承中構造函數的訪問特點
明確一點:new 一個子類對象,會先去調用父類的構造方法,再調用子類的構造方法。沒有子類,哪來父類。所以必須先有父類。所以在子類的構造方法中,一定要調用父類構造方法。
- 情形一:父類沒寫構造函數(沒寫會默認贈送一個無參構造)或者只寫了一個無參構造。那麼在子類的構造方法必須調用父類的無參構造。如果子類也沒寫構造函數:沒寫會默認贈送一個無參構造,並且在其中調用父類的無參構造。如果子類顯式寫了無參構造,那就必須調用父類的無參構造。如果子類寫了有參構造,那就必須調用父類的無參構造。
public class Fu {
public Fu() {}//不寫會贈送一個無參構造,如果寫了有參構造就不會贈送了
}
public class Zi extends Fu {
public Zi() {//不寫會贈送一個無參構造,如果寫了有參構造就不會贈送了
super();//這行代碼不寫也行,會默認贈送一個
}
//這兩個構造函數都行,明確我們的目的:構造出一個父類對象
public Zi(Object obj) {
super();//這行代碼不寫也行,會默認贈送一個
}
}
- 情形二:父類只有有參構造。此時不會給父類贈送一個無參構造了。所以在我們的子類構造函數中,必須調用父類的有參構造。
public class Fu {
public Fu(Object obj) {}
}
public class Zi extends Fu {
public Zi() {
super(null);
}
//這兩種構造函數都行
public Zi(Object obj) {
super(obj);
}
}
- 情形三:父類顯式寫出無參構造,同時還有有參構造。子類此時沒寫構造函數,沒寫會默認贈送一個無參構造,並且在其中調用父類的無參構造。子類此時寫了構造函數,可以在構造函數中調用父類的無參構造,也可以調用父類的有參構造。
public class Fu {
public Fu() {}
public Fu(Object obj) {}
}
public class Zi extends Fu {}//不會報錯
public class Zi extends Fu {
public Zi() {}//會贈送一個super()
}
public class Zi extends Fu {
public Zi() {
super(null);
}
//這兩個構造函數都行
public Zi(Object obj) {
super(obj);
}
}