主要內容
- 繼承
- 多態
- 向上轉型
- 向下轉型
- 方法重寫
- 方法重載
- super關鍵字
- 動態綁定
- 封裝訪問控制
- 構造方法規則
一、繼承
1. 概念:
一句話說就是:“共性抽取,代碼復用”
-
- 子類會將父類中的成員變量或者成員方法繼承到子類中
-
- 子類繼承父類之后,必須要新添加自己特有的成員,體現出與基類的不同,否則就沒有必要繼承了
2. 語法:
(1) extends
修飾符 class 子類 extends 父類{//...
}
(2) 父類成員訪問:
訪問1:子類中訪問父類的成員變量
-
- 子類和父類不存在同名成員變量
- 正常訪問
-
- 子類和父類成員變量同名
循就近原則,自己有優先自己的,如果沒有則向父類中找。
-
如果訪問的同名成員變量子類中有,優先訪問子類自己的成員變量。
-
如果訪問的成員變量子類中無,則訪問父類繼承下來的,如果父類也沒有定義,則編譯報錯。
-
如果訪問的成員變量與父類中成員變量同名,則優先訪問自己的
訪問2:子類中訪問父類的成員方法
-
成員方法名字不同
- 成員方法沒有同名時,在子類方法中或者通過子類對象訪問方法時,則優先訪問自己的,自己沒有時再到父類中找,如果父類中也沒有則報錯。
-
成員方法名字相同
class Parent {void show(int x) { System.out.println("Parent:int"); } }class Child extends Parent {void show(String s) { System.out.println("Child:String"); } // 重載void demo() {show(10); // 調用父類方法show("hello"); // 調用子類重載方法} }
- 通過子類對象訪問父類與子類中不同名方法時,優先在子類中找,找到則訪問,否則在父類中找,找到則訪問,否則編譯報錯。
- 通過子類對象訪問父類與子類同名方法時,如果父類和子類同名方法的參數列表不同(重載),根據調用方法適傳遞的參數選擇合適的方法訪問,如果沒有則報錯
class Base {int a = 10;String b = "父類";void methodA() { System.out.println("父類方法A"); }
}class Derived extends Base {int a = 20; // 同名成員變量void methodA() { System.out.println("子類重寫方法A"); } // 方法重寫void methodB() { System.out.println("子類特有方法B"); }void test() {System.out.println(a); // 輸出20(就近原則)System.out.println(super.a); // 輸出10(顯式訪問父類)methodA(); // 調用子類重寫方法super.methodA(); // 調用父類被覆蓋方法}
}
3. super
-
在子類方法中訪問父類的成員。
-
只能在==非靜態方法==中使用
-
在子類方法中,訪問父類的成員變量和方法。
-
super
和this
:class Animal {String type = "Animal"; }class Cat extends Animal {String type = "Cat";void print() {System.out.println(this.type); // Cat(當前對象成員)System.out.println(super.type); // Animal(父類成員)} }
-
this
是當前對象的引用,當前對象即調用實例方法的對象,super
相當于是子類對象中從父類繼承下來部分成員的引用
-
- 在非靜態成員方法中,
this
用來訪問本類的方法和屬性,super
用來訪問父類繼承下來的方法和屬性
- 在非靜態成員方法中,
-
- 在構造方法中:
this(...)
用于調用本類構造方法,super(...)
用于調用父類構造方法,兩種調用不能同時在構造方法中出現
- 在構造方法中:
-
- 子類構造方法中一定會存在
super(...)
的調用,用戶沒有寫編譯器也會增加,但是this(...)
用戶不寫則沒有
- 子類構造方法中一定會存在
-
-
4. 子類構造方法:
子類對象構造時,需要先調用基類構造方法,然后執行子類的構造方法。
- 在構造子類對象時候 ,先要調用基類的構造方法,將從基類繼承下來的成員構造完整,然后再調用子類自己的構造方法,將子類自己新增加的成員初始化完整
class Person {String name;Person(String name) { this.name = name; System.out.println("Person構造");}
}class Student extends Person {int id;Student(String name, int id) {super(name); // 必須第一行!this.id = id;System.out.println("Student構造");}
}public class Main {public static void main(String[] args) {Student s = new Student("張三", 1001);}
}
輸出:
Person構造
Student構造
-
注意:
-
- 若父類顯式定義無參或者默認的構造方法,在子類構造方法第一行默認有隱含的
super()
調用,即調用基類構造方法
- 若父類顯式定義無參或者默認的構造方法,在子類構造方法第一行默認有隱含的
-
- 如果父類構造方法是帶有參數的,此時需要用戶為子類顯式定義構造方法,并在子類構造方法中選擇合適的父類構造方法調用,否則編譯失敗。
-
- 在子類構造方法中,
super(...)
調用父類構造時,必須是子類構造函數中第一條語句。并且只能出現一次。
- 在子類構造方法中,
-
super(...)
只能在子類構造方法中出現一次,并且不能和this同時出現
-
二、封裝
(只針對Java,不同語言有些許差異)
No | 范圍 | private | default | protected | public |
---|---|---|---|---|---|
1 | 同一包中的同一類 | ? | ? | ? | ? |
2 | 同一包中的不同類 | ? | ? | ? | |
3 | 不同包中的子類 | ? | ? | ||
4 | 不同包中的非子類 | ? |
三、多態
一句話說就是:完成某個行為,當不同的對象去完成時會產生出不同 的狀態。
(1) 重寫(override):
-
也稱為覆蓋。重寫是子類對父類非靜態、非
private
修飾,非final
修飾,非構造方法等的實現過程進行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫! -
注意:
-
子類在重寫父類的方法時,一般必須與父類方法原型一致: 返回值類型 方法名 (參數列表) 要完全一致
-
被重寫的方法返回值類型可以不同,但是必須是具有父子關系的
-
訪問權限不能比父類中被重寫的方法的訪問權限更低。例如:如果父類方法被
public
修飾,則子類中重寫該方法就不能聲明為protected
-
父類被
static
、private
修飾的方法、構造方法都不能被重寫。 -
重寫的方法, 可以使用
@Override
注解來顯式指定. 有了這個注解能幫我們進行一些合法性校驗. 例如不小心將方法名字拼寫錯了 (比如寫成aet
), 那么此時編譯器就會發現父類中沒有aet
方法, 就會編譯報錯, 提示無法構成重寫.
-
(2) 重寫和重載:
區別點 | 重寫(override) | 重載(overload) |
---|---|---|
參數列表 | 一定不能修改 | 必須修改 |
返回類型 | 不能修改(除非是父子類兼容的返回類型) | 可以修改 |
訪問限定符 | 不能做更嚴格的限制(可以相同或更寬松) | 可以修改 |
(3) 靜態綁定和動態綁定:
-
靜態綁定:也稱為前期綁定(早綁定),即在編譯時,根據用戶所傳遞實參類型就確定了具體調用那個方法。典型代表函數重載。
-
動態綁定:也稱為后期綁定(晚綁定),即在編譯時,不能確定方法的行為,需要等到程序運行時,才能夠確定具體調用那個類的方法。
(4) 多態實現條件:
-
- 必須在繼承體系下
-
- 子類必須要對父類中方法進行重寫
-
- 通過父類的引用調用重寫的方法
父類類型 變量名 = new 子類對象(); 變量名.方法(); // 實際調用的是子類重寫的方法
class Animal { void sound() { System.out.println("動物叫"); } } class Cat extends Animal { @Override void sound() { System.out.println("喵喵喵"); } // 重寫 } public class Main { public static void main(String[] args) { Animal a = new Cat(); // 向上轉型 a.sound(); // 輸出"喵喵喵"(動態綁定,執行子類方法) } }
四、向上轉移和向下轉型:
1. 向上轉型:
從小范圍向大范圍的轉換
-
實際就是創建一個子類對象,將其當成父類對象來使用。
-
父類類型 對象名 = new 子類類型()
Animal animal = new Cat("kitty",2)
-
使用:
-
-
直接賦值
Animal cat = new Cat("kitty",2); //子類對象直接賦值給父類對象
-
-
- 方法傳參
public static void eatFood(Animal a){a.eat(); }
-
-
方法返回
public static Animal getAnimal(String type) {if ("dog".equals(type)) {return new Dog(); // 向上轉型:Dog→Animal} else if ("bird".equals(type)) {return new Bird(); // 向上轉型:Bird→Animal}return new Animal();}
-
-
2. 向下轉型(有風險):
將父類引用再還原為子類對象,即向下轉換
public class Main {public static void main(String[] args) {Animal animal = new Cat(); // 向上轉型// 向下轉型(必須先向上轉!)if (animal instanceof Cat) { // 安全檢查Cat cat = (Cat) animal; // 強制轉換cat.catchMouse(); // 輸出"貓抓老鼠"}// 錯誤示例:直接轉型非子類對象(運行時拋出ClassCastException)// Animal dog = new Animal();// Cat wrongCat = (Cat) dog; // 報錯!}
}
注意:
- 必須通過
instanceof
檢查安全性,避免ClassCastException
。 - 只有原本是子類對象的父類引用才能成功向下轉型。