繼承的實現:代碼複用的核心機制
當你需要為貓、狗、鳥等動物類編寫代碼時,是否發現它們都有顏色、叫聲等共同特徵?如果每個類都重複定義這些屬性和方法,不僅冗餘還難以維護。繼承就是為解決這類問題而生的——它允許子類"複用"父類的代碼,並在此基礎上添加新功能。
繼承的基本語法
在 Java 中使用 extends 關鍵字實現繼承,語法格式如下:
// 父類(基類)
public class Animal {
// 共同屬性
private String color;
// 共同方法
public void shout() {
System.out.println("動物發出叫聲");
}
// getter和setter方法
public String getColor() { return color; }
public void setColor(String color) { this.color = color; }
}
// 子類(派生類)
public class Dog extends Animal {
// 子類特有方法
public void wagTail() {
System.out.println("狗搖尾巴");
}
}
上述代碼中,Dog 類通過 extends Animal 繼承了 Animal 類的 color 屬性和 shout() 方法,同時新增了 wagTail() 方法。這種結構就像現實中的"狗是動物的一種"關係。
類繼承關係圖
Java 只支持單繼承(一個子類只能有一個直接父類),但支持多層繼承。以下是常見的繼承類型示意圖:
關鍵點:
- 單級繼承:如 Dog extends Animal
- 多級繼承:如 Puppy extends Dog(小狗是狗的一種)
- 分層繼承:如 Cat 和 Dog 都繼承 Animal
- ❌ Java 不支持多重繼承(一個類不能同時繼承多個父類)
繼承的好處
- 代碼複用:無需重複編寫父類已有的代碼
- 擴展性:子類可在父類基礎上添加新功能
- 維護性:修改父類代碼,所有子類自動受益
方法重寫與重載:多態的實現基礎
繼承讓子類獲得了父類的方法,但有時需要修改這些方法的行為(如狗和貓的叫聲不同),這就需要方法重寫;而方法重載則是在同一個類中定義多個同名但參數不同的方法。
方法重寫(Override)
當子類需要修改父類方法的實現時,使用方法重寫。例如不同動物的叫聲不同:
// 父類
public class Animal {
public void shout() {
System.out.println("動物發出叫聲");
}
}
// 子類重寫父類方法
public class Dog extends Animal {
@Override // 註解:明確標識這是重寫的方法
public void shout() {
System.out.println("汪汪汪");
}
}
public class Cat extends Animal {
@Override
public void shout() {
System.out.println("喵喵喵");
}
}
運行以下代碼會得到不同結果:
public class Test {
public static void main(String[] args) {
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.shout(); // 輸出:汪汪汪(實際調用的是Dog類的方法)
a2.shout(); // 輸出:喵喵喵(實際調用的是Cat類的方法)
}
}
方法重寫規則表
核心規則:
- 方法簽名必須相同:方法名、參數列表完全一致(返回值類型需兼容)
- 訪問權限不能降低:如父類是 public,子類不能改為 private
- 不能拋出更寬泛的異常:子類拋出的異常範圍不能大於父類
- 用 @Override 註解:幫助編譯器檢查重寫是否正確
方法重載(Overload)
在同一個類中,允許存在多個同名方法,但參數列表必須不同(參數類型、個數或順序不同):
public class Calculator {
// 兩個整數相加
public int add(int a, int b) {
return a + b;
}
// 三個整數相加(參數個數不同)
public int add(int a, int b, int c) {
return a + b + c;
}
// 兩個小數相加(參數類型不同)
public double add(double a, double b) {
return a + b;
}
}
重寫與重載的區別
表格
|
特性 |
方法重寫(Override) |
方法重載(Overload) |
|
發生位置 |
子類與父類之間 |
同一個類中 |
|
方法名稱 |
必須相同 |
必須相同 |
|
參數列表 |
必須相同 |
必須不同(類型/個數/順序) |
|
返回值類型 |
子類 <= 父類(兼容類型) |
可以不同 |
|
訪問修飾符 |
子類 >= 父類 |
可以不同 |
|
多態性體現 |
運行時多態 |
編譯時多態 |
super關鍵字:訪問父類成員的橋樑
當子類重寫了父類方法或隱藏了父類屬性時,如何訪問父類的版本?super關鍵字就是專門用於調用父類成員的工具。
super的三種用法
- 調用父類構造方法:super(參數),必須放在子類構造方法第一行
- 調用父類普通方法:super.方法名(參數)
- 訪問父類屬性:super.屬性名
public class Animal {
private String name;
// 父類構造方法
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + "在吃東西");
}
}
public class Dog extends Animal {
private int age;
// 子類構造方法
public Dog(String name, int age) {
super(name); // 調用父類構造方法
this.age = age;
}
@Override
public void eat() {
super.eat(); // 調用父類的eat方法
System.out.println("狗喜歡啃骨頭");
}
}
super與this的區別
表格
|
關鍵字 |
訪問對象 |
調用構造方法 |
查找範圍 |
|
this |
當前對象 |
this(參數) |
當前類 -> 父類 |
|
super |
父類對象 |
super(參數) |
直接父類 |
訪問修飾符與繼承:控制成員的可見範圍
在第五天我們學習了四種訪問修飾符,它們在繼承中有着特殊的作用——決定父類成員能否被子類訪問。
訪問修飾符權限表
表格
|
修飾符 |
本類 |
同包 |
子類 |
其他包 |
|
private |
✅ |
❌ |
❌ |
❌ |
|
default(缺省) |
✅ |
✅ |
❌ |
❌ |
|
protected |
✅ |
✅ |
✅ |
❌ |
|
public |
✅ |
✅ |
✅ |
✅ |
繼承中的訪問規則
- private成員:子類完全不可見,需通過父類的getter/setter訪問
- protected成員:不同包子類只能通過繼承關係訪問(不能用父類引用訪問)
- 構造方法:不能繼承,但子類可通過super調用
// 父類(com.animal包)
package com.animal;
public class Animal {
private String color; // 私有成員(子類不可見)
protected String name; // 保護成員(子類可見)
public int age; // 公共成員(全部可見)
public String getColor() { // 公共方法訪問私有屬性
return color;
}
}
// 子類(com.pet包)
package com.pet;
import com.animal.Animal;
public class Dog extends Animal {
public void showInfo() {
// System.out.println(color); // 編譯錯誤:private成員不可見
System.out.println(name); // 正確:protected成員可訪問
System.out.println(age); // 正確:public成員可訪問
System.out.println(getColor()); // 正確:通過公共方法訪問私有屬性
}
}
final關鍵字:不可變的終極修飾符
如果你希望某個類不能被繼承,或者某個方法不能被重寫,final關鍵字可以幫你實現。它就像給代碼加上"不可修改"的封印。
final的四種用法
- 修飾類:該類不能被繼承(如 String 類)
public final class MathUtil {
// 工具類通常設為final,避免被繼承篡改
}
- 修飾方法:該方法不能被重寫
public class Animal {
public final void breathe() {
System.out.println("用肺呼吸"); // 核心方法不允許重寫
}
}
- 修飾變量:變量值不能被修改(常量)
public class Circle {
public static final double PI = 3.1415926; // 靜態常量
private final String id; // 實例常量
public Circle(String id) {
this.id = id; // 只能在構造方法中賦值
}
}
- 修飾參數:方法參數在方法內不能被修改
public void printInfo(final String name) {
// name = "新名字"; // 編譯錯誤:final參數不可修改
System.out.println(name);
}
final與繼承的關係
- final類沒有子類,因此所有方法默認都是final的
- final方法可以被繼承,但不能被重寫
- final變量在繼承中可被子類使用,但不能被修改
繼承中的構造方法執行順序
當創建子類對象時,父類構造方法會先於子類構造方法執行——這是因為子類依賴父類的初始化。我們通過一個案例來觀察完整執行流程:
構造方法執行順序示例
public class Animal {
public Animal() {
System.out.println("1. Animal無參構造");
}
public Animal(String name) {
System.out.println("1. Animal有參構造:" + name);
}
}
public class Dog extends Animal {
public Dog() {
// super(); // 隱式調用父類無參構造
System.out.println("2. Dog無參構造");
}
public Dog(String name) {
super(name); // 顯式調用父類有參構造
System.out.println("2. Dog有參構造:" + name);
}
}
// 測試類
public class Test {
public static void main(String[] args) {
System.out.println("創建Dog對象:");
Dog dog = new Dog("旺財");
}
}
執行結果:
創建Dog對象:
1. Animal有參構造:旺財
2. Dog有參構造:旺財
繼承中的構造方法執行順序示意圖
執行規則:
- 調用父類構造方法(顯式用 super(參數),隱式調用無參 super())
- 初始化子類成員變量
- 執行子類構造方法體
⚠️ 注意:如果父類沒有無參構造,子類必須顯式調用父類有參構造,否則編譯錯誤!
實踐案例:動物類繼承體系
現在我們綜合運用今天所學知識,設計一個動物類繼承體系,包含以下功能:
- 父類 Animal 定義共同屬性和方法
- 子類 Dog、Cat 重寫父類方法並添加特有功能
- 使用 super 調用父類構造方法
- 用 final 定義不可修改的常量
動物類繼承體系代碼實現
// 動物父類
public class Animal {
protected String name;
protected String color;
public static final int LEG_COUNT = 4; // 動物腿數常量(默認4條腿)
public Animal(String name, String color) {
this.name = name;
this.color = color;
}
public void shout() {
System.out.println(name + "發出叫聲");
}
public void eat() {
System.out.println(name + "在進食");
}
}
// 狗子類
public class Dog extends Animal {
private String breed; // 品種
public Dog(String name, String color, String breed) {
super(name, color); // 調用父類構造方法
this.breed = breed;
}
@Override
public void shout() {
System.out.println(name + "汪汪叫");
}
public void guardHouse() {
System.out.println(name + "正在看家");
}
}
// 貓子類
public class Cat extends Animal {
private int age; // 年齡
public Cat(String name, String color, int age) {
super(name, color); // 調用父類構造方法
this.age = age;
}
@Override
public void shout() {
System.out.println(name + "喵喵叫");
}
public void catchMouse() {
System.out.println(name + "抓老鼠");
}
}
// 測試類
public class AnimalTest {
public static void main(String[] args) {
Animal dog = new Dog("旺財", "黃色", "金毛");
Animal cat = new Cat("咪咪", "白色", 3);
dog.shout(); // 多態:調用Dog的shout
dog.eat(); // 繼承:調用Animal的eat
((Dog) dog).guardHouse(); // 向下轉型,調用Dog特有方法
cat.shout(); // 多態:調用Cat的shout
((Cat) cat).catchMouse(); // 向下轉型,調用Cat特有方法
System.out.println("動物腿數:" + Animal.LEG_COUNT); // 訪問常量
}
}
動物類繼承關係圖
運行結果:
旺財汪汪叫
旺財在進食
旺財正在看家
咪咪喵喵叫
咪咪抓老鼠
動物腿數:4
總結與明日預告
今天我們學習了面向對象的核心特性——繼承,通過 extends 實現代碼複用,用 super 訪問父類成員,用 final 限制修改,還掌握了方法重寫與重載的區別。這些知識讓我們的代碼從"零散類"升級為"有機體系",為明日學習多態打下基礎。
今日重點:
- 繼承通過 extends 實現,子類複用父類代碼並擴展新功能
- 方法重寫要求籤名相同,實現運行時多態
- super用於調用父類構造、方法和屬性
- 訪問修飾符控制繼承中的成員可見性
- final可修飾類、方法、變量,實現不可變特性
明日內容:多態的實現原理、抽象類與接口、Object類常用方法。我們將學習如何用多態設計靈活的代碼架構,敬請期待!
課後練習:
- 設計一個Shape父類,包含計算面積和周長的方法,然後派生出Circle、Rectangle子類實現具體計算
- 嘗試用final修飾一個工具類,體會不可繼承的特性
- 思考:為什麼Java不支持多重繼承?(提示:菱形繼承問題)