引言
????????在 Java 面向對象編程中,繼承與多態是兩大核心特性,它們共同支撐了代碼的復用性、擴展性和靈活性。本章將從繼承的基本實現開始,逐步深入到方法覆蓋、訪問控制、抽象類等概念,最終揭示多態的本質與應用。通過大量可運行的代碼示例和直觀的圖表,幫助你徹底掌握這些重要知識點。
7.1 類的繼承
????????繼承是面向對象編程的三大特性之一,它允許我們基于已有的類創建新類,從而實現代碼復用和擴展。被繼承的類稱為父類(超類),新創建的類稱為子類(派生類)。
7.1.1 類繼承的實現
在 Java 中,使用extends
關鍵字實現類的繼承,語法如下:
class 子類名 extends 父類名 {// 子類新增屬性和方法
}
核心特點:
- 子類擁有父類的非私有屬性和方法(代碼復用)
- 子類可以新增自己的屬性和方法(功能擴展)
- Java 只支持單繼承(一個子類只能有一個直接父類)
代碼示例:基礎繼承實現
// 父類:動物
class Animal {// 父類屬性protected String name;// 父類方法public void eat() {System.out.println(name + "正在吃東西");}public void sleep() {System.out.println(name + "正在睡覺");}
}// 子類:狗(繼承自動物)
class Dog extends Animal {// 子類新增屬性private String breed; // 品種// 子類新增方法public void bark() {System.out.println(name + "在汪汪叫");}// 子類setter方法(設置名字和品種)public void setInfo(String name, String breed) {this.name = name; // 直接訪問父類的protected屬性this.breed = breed;}public void showBreed() {System.out.println("品種:" + breed);}
}// 測試類
public class InheritanceDemo {public static void main(String[] args) {// 創建子類對象Dog dog = new Dog();dog.setInfo("旺財", "金毛");// 調用父類繼承的方法dog.eat(); // 輸出:旺財正在吃東西dog.sleep(); // 輸出:旺財正在睡覺// 調用子類新增的方法dog.bark(); // 輸出:旺財在汪汪叫dog.showBreed();// 輸出:品種:金毛}
}
類圖:Animal 與 Dog 的繼承關系
@startuml
class Animal {- String name+ void eat()+ void sleep()
}class Dog {- String breed+ void bark()+ void setInfo(String, String)+ void showBreed()
}Animal <|-- Dog : extends
@enduml
7.1.2 方法覆蓋
????????當子類需要修改父類的方法實現時,可以使用方法覆蓋(Override),也稱為方法重寫。
方法覆蓋的規則:
- 方法名、參數列表必須與父類完全相同
- 返回值類型:父類返回值為
T
,子類可以是T
或T的子類
(協變返回) - 訪問權限:子類方法權限不能低于父類(如父類
protected
,子類可protected
或public
) - 不能拋出比父類更多的 checked 異常
- 用
@Override
注解顯式聲明(非必須,但推薦,編譯器會校驗正確性)
代碼示例:方法覆蓋實現
// 父類:形狀
class Shape {// 父類方法:計算面積(默認實現)public double calculateArea() {System.out.println("形狀的面積計算");return 0.0;}
}// 子類:圓形(重寫面積計算方法)
class Circle extends Shape {private double radius; // 半徑public Circle(double radius) {this.radius = radius;}// 重寫父類的面積計算方法@Overridepublic double calculateArea() {System.out.println("圓形的面積計算");return Math.PI * radius * radius; // 圓面積公式:πr2}
}// 子類:矩形(重寫面積計算方法)
class Rectangle extends Shape {private double length; // 長private double width; // 寬public Rectangle(double length, double width) {this.length = length;this.width = width;}// 重寫父類的面積計算方法@Overridepublic double calculateArea() {System.out.println("矩形的面積計算");return length * width; // 矩形面積公式:長×寬}
}// 測試類
public class OverrideDemo {public static void main(String[] args) {Shape circle = new Circle(5);System.out.println("圓面積:" + circle.calculateArea()); // 輸出:圓形的面積計算 圓面積:78.539...Shape rectangle = new Rectangle(4, 6);System.out.println("矩形面積:" + rectangle.calculateArea()); // 輸出:矩形的面積計算 矩形面積:24.0}
}
7.1.3 super 關鍵字
super
關鍵字用于訪問父類的屬性、方法和構造器,主要場景:
- 調用父類的非私有屬性:
super.屬性名
- 調用父類的非私有方法:
super.方法名(參數)
- 調用父類的構造器:
super(參數)
(必須放在子類構造器第一行)
代碼示例:super 關鍵字的使用
// 父類:員工
class Employee {protected String name;protected double salary;// 父類構造器public Employee(String name, double salary) {this.name = name;this.salary = salary;}// 父類方法public void showInfo() {System.out.println("姓名:" + name + ",薪資:" + salary);}
}// 子類:經理(繼承自員工)
class Manager extends Employee {private double bonus; // 獎金// 子類構造器public Manager(String name, double salary, double bonus) {super(name, salary); // 調用父類構造器(必須在第一行)this.bonus = bonus;}// 重寫父類方法,并用super調用父類方法@Overridepublic void showInfo() {super.showInfo(); // 調用父類的showInfo()System.out.println("獎金:" + bonus + ",總薪資:" + (salary + bonus));}// 子類方法:使用super訪問父類屬性public void raiseSalary(double percent) {// 父類salary是protected,子類可通過super訪問salary = salary * (1 + percent / 100) + bonus; System.out.println(name + "的薪資已調整為:" + salary);}
}// 測試類
public class SuperDemo {public static void main(String[] args) {Manager manager = new Manager("張三", 8000, 2000);manager.showInfo(); // 輸出:// 姓名:張三,薪資:8000.0// 獎金:2000.0,總薪資:10000.0manager.raiseSalary(10); // 漲薪10%// 輸出:張三的薪資已調整為:9800.0}
}
7.1.4 調用父類的構造方法
????????子類構造器中,默認會隱式調用父類的無參構造器(super()
);如果父類沒有無參構造器,子類必須顯式調用父類的有參構造器(super(參數)
),否則編譯報錯。
流程圖:構造器調用順序
代碼示例:父類構造器調用
// 父類:Person
class Person {private String name;private int age;// 父類有參構造器(注意:沒有無參構造器)public Person(String name, int age) {this.name = name;this.age = age;System.out.println("Person構造器被調用:" + name + "," + age + "歲");}
}// 子類:Student(繼承自Person)
class Student extends Person {private String school; // 學校// 子類構造器:必須顯式調用父類有參構造器public Student(String name, int age, String school) {super(name, age); // 顯式調用父類構造器(否則編譯報錯)this.school = school;System.out.println("Student構造器被調用:" + school);}
}// 測試類
public class ConstructorCallDemo {public static void main(String[] args) {// 創建子類對象時,先調用父類構造器,再調用子類構造器Student student = new Student("李四", 18, "北京大學");// 輸出:// Person構造器被調用:李四,18歲// Student構造器被調用:北京大學}
}
7.1 綜合案例:動物繼承體系
需求:設計動物繼承體系,包含父類Animal
,子類Dog
和Cat
,展示繼承、方法覆蓋和 super 關鍵字的綜合應用。
類圖
@startuml
class Animal {- String name+ Animal(String name)+ void eat()+ void makeSound()+ void sleep()
}class Dog {+ Dog(String name)+ void makeSound()+ void fetch()
}class Cat {+ Cat(String name)+ void makeSound()+ void climbTree()
}Animal <|-- Dog
Animal <|-- Cat
@enduml
完整代碼
// 父類:動物
class Animal {protected String name; // 名字// 父類構造器public Animal(String name) {this.name = name;System.out.println("Animal構造器:" + name);}// 吃東西(通用實現)public void eat() {System.out.println(name + "在吃東西");}// 發出聲音(父類默認實現)public void makeSound() {System.out.println(name + "發出聲音");}// 睡覺(通用實現)public void sleep() {System.out.println(name + "在睡覺");}
}// 子類:狗
class Dog extends Animal {// 子類構造器public Dog(String name) {super(name); // 調用父類構造器System.out.println("Dog構造器:" + name);}// 重寫:狗的叫聲@Overridepublic void makeSound() {System.out.println(name + "汪汪叫");}// 子類特有方法:撿東西public void fetch() {System.out.println(name + "在撿球");super.eat(); // 調用父類的eat()方法}
}// 子類:貓
class Cat extends Animal {// 子類構造器public Cat(String name) {super(name); // 調用父類構造器System.out.println("Cat構造器:" + name);}// 重寫:貓的叫聲@Overridepublic void makeSound() {System.out.println(name + "喵喵叫");}// 子類特有方法:爬樹public void climbTree() {System.out.println(name + "在爬樹");}
}// 測試類
public class AnimalInheritanceDemo {public static void main(String[] args) {System.out.println("===== 創建Dog對象 =====");Dog dog = new Dog("旺財");dog.eat(); // 繼承父類方法dog.makeSound();// 調用重寫的方法dog.sleep(); // 繼承父類方法dog.fetch(); // 子類特有方法System.out.println("\n===== 創建Cat對象 =====");Cat cat = new Cat("咪咪");cat.eat(); // 繼承父類方法cat.makeSound();// 調用重寫的方法cat.sleep(); // 繼承父類方法cat.climbTree();// 子類特有方法}
}
運行結果:
7.2 封裝性與訪問修飾符
????????封裝是將數據和操作數據的方法捆綁在一起,并通過訪問修飾符控制外部訪問權限,實現 "數據隱藏"。
7.2.1 類的訪問權限
Java 中類的訪問權限只有兩種:
public
:公開類,可被所有包中的類訪問- 默認權限(無修飾符):包內可見,僅同一包中的類可訪問
規則:
- 一個 Java 源文件中最多有一個
public
類,且文件名必須與public
類名相同 - 若類為
public
,其包路徑需與文件夾結構一致
代碼示例:類訪問權限
// 文件:com/example/PublicClass.java(public類)
package com.example;
public class PublicClass {public void publicMethod() {System.out.println("public類的public方法");}
}// 文件:com/example/DefaultClass.java(默認權限類)
package com.example;
class DefaultClass { // 無訪問修飾符,默認權限public void defaultClassMethod() {System.out.println("默認類的public方法");}
}// 文件:com/other/TestClass.java(不同包的測試類)
package com.other;
import com.example.PublicClass;
// import com.example.DefaultClass; // 編譯報錯:DefaultClass是默認權限,不同包不可訪問public class TestClass {public static void main(String[] args) {PublicClass publicObj = new PublicClass();publicObj.publicMethod(); // 正常訪問:public類可跨包訪問// DefaultClass defaultObj = new DefaultClass(); // 編譯報錯:無法訪問默認權限類}
}
7.2.2 類成員的訪問權限
類成員(屬性和方法)有 4 種訪問權限,權限從大到小為:
修飾符 | 本類 | 同包類 | 不同包子類 | 其他類 |
---|---|---|---|---|
public | ?? | ?? | ?? | ?? |
protected | ?? | ?? | ?? | ? |
默認 | ?? | ?? | ? | ? |
private | ?? | ? | ? | ? |
最佳實踐:
- 屬性通常用
private
修飾,通過public
的getter/setter
方法訪問 - 方法根據需要設置權限,對外暴露的接口用
public
,內部工具方法用private
- 父子類共享的方法 / 屬性用
protected
代碼示例:成員訪問權限
// 父類:com/example/Parent.java
package com.example;
public class Parent {public String publicField = "public屬性";protected String protectedField = "protected屬性";String defaultField = "default屬性"; // 默認權限private String privateField = "private屬性";public void publicMethod() {System.out.println("public方法:" + privateField); // 本類可訪問private}protected void protectedMethod() {System.out.println("protected方法");}void defaultMethod() {System.out.println("default方法");}private void privateMethod() {System.out.println("private方法");}
}// 同包子類:com/example/ChildSamePackage.java
package com.example;
public class ChildSamePackage extends Parent {public void accessParent() {System.out.println(publicField); // ?? publicSystem.out.println(protectedField); // ?? protectedSystem.out.println(defaultField); // ?? 同包默認權限// System.out.println(privateField); // ? 不可訪問privatepublicMethod(); // ??protectedMethod(); // ??defaultMethod(); // ?? 同包// privateMethod(); // ?}
}// 不同包子類:com/other/ChildDifferentPackage.java
package com.other;
import com.example.Parent;
public class ChildDifferentPackage extends Parent {public void accessParent() {System.out.println(publicField); // ?? publicSystem.out.println(protectedField); // ?? protected(子類)// System.out.println(defaultField); // ? 不同包默認權限不可訪問// System.out.println(privateField); // ?publicMethod(); // ??protectedMethod(); // ?? 子類可訪問// defaultMethod(); // ? 不同包默認方法不可訪問// privateMethod(); // ?}
}// 不同包非子類:com/other/OtherClass.java
package com.other;
import com.example.Parent;
public class OtherClass {public void accessParent() {Parent parent = new Parent();System.out.println(parent.publicField); // ?? public// System.out.println(parent.protectedField); // ? 非子類不可訪問protected// System.out.println(parent.defaultField); // ? 不同包默認不可訪問// System.out.println(parent.privateField); // ?parent.publicMethod(); // ??// parent.protectedMethod(); // ? 非子類不可訪問// parent.defaultMethod(); // ?// parent.privateMethod(); // ?}
}
7.2 綜合案例:封裝與訪問控制
需求:設計一個User
類,通過訪問修飾符實現封裝,提供安全的屬性訪問方式。
完整代碼
package com.example.encapsulation;// 用戶類(封裝示例)
public class User {// 屬性私有化(private)private String username; // 用戶名private String password; // 密碼private int age; // 年齡// 無參構造器public User() {}// 有參構造器public User(String username, String password, int age) {this.username = username;this.password = password;this.age = age;}// 用戶名的getter(public,對外提供讀取權限)public String getUsername() {return username;}// 密碼的getter(僅返回脫敏后的密碼)public String getPasswordMasked() {if (password == null || password.length() <= 2) {return "***";}return password.substring(0, 2) + "***"; // 前2位顯示,其余脫敏}// 密碼的setter(提供修改權限,帶簡單驗證)public void setPassword(String password) {if (password == null || password.length() < 6) {throw new IllegalArgumentException("密碼長度不能少于6位");}this.password = password;}// 年齡的getterpublic int getAge() {return age;}// 年齡的setter(帶驗證邏輯)public void setAge(int age) {if (age < 0 || age > 150) {throw new IllegalArgumentException("年齡必須在0-150之間");}this.age = age;}// 公開方法:用戶登錄public boolean login(String inputPassword) {return password.equals(inputPassword); // 內部可訪問private屬性}
}// 測試類
public class EncapsulationDemo {public static void main(String[] args) {User user = new User("zhangsan", "123456", 25);// 訪問用戶名(通過getter)System.out.println("用戶名:" + user.getUsername()); // 輸出:用戶名:zhangsan// 訪問脫敏密碼System.out.println("密碼(脫敏):" + user.getPasswordMasked()); // 輸出:密碼(脫敏):12***// 測試年齡設置user.setAge(30);System.out.println("年齡:" + user.getAge()); // 輸出:年齡:30// 測試密碼設置(合法)user.setPassword("newpass123");System.out.println("修改密碼后(脫敏):" + user.getPasswordMasked()); // 輸出:ne***// 測試登錄boolean loginSuccess = user.login("newpass123");System.out.println("登錄成功?" + loginSuccess); // 輸出:true// 測試非法年齡(會拋出異常)try {user.setAge(200);} catch (IllegalArgumentException e) {System.out.println("年齡設置錯誤:" + e.getMessage()); // 輸出:年齡必須在0-150之間}}
}
7.3 防止類擴展和方法覆蓋
final
關鍵字用于限制類、方法或變量的修改,實現 "不可變" 特性。
7.3.1 final 修飾類
final
修飾的類不能被繼承(最終類),確保類的功能不被修改。
典型應用:
- JDK 中的
String
、Integer
等類都是final
類 - 工具類通常設計為
final
(如java.util.Math
)
代碼示例:final 類
// final類:不能被繼承
final class FinalClass {public void show() {System.out.println("這是final類的方法");}
}// 嘗試繼承final類(編譯報錯)
// class SubClass extends FinalClass { // 錯誤:無法從最終類FinalClass繼承
// @Override
// public void show() {
// System.out.println("嘗試覆蓋final類的方法");
// }
// }// 測試類
public class FinalClassDemo {public static void main(String[] args) {FinalClass obj = new FinalClass();obj.show(); // 輸出:這是final類的方法}
}
7.3.2 final 修飾方法
final
修飾的方法不能被子類覆蓋,但類可以被繼承。
應用場景:
- 確保核心方法的實現不被修改
- 提升性能(JVM 可能對 final 方法進行優化)
代碼示例:final 方法
// 父類:包含final方法
class ParentWithFinalMethod {// final方法:不能被覆蓋public final void finalMethod() {System.out.println("這是final方法,不能被覆蓋");}// 普通方法:可以被覆蓋public void normalMethod() {System.out.println("這是普通方法,可以被覆蓋");}
}// 子類
class ChildWithFinalMethod extends ParentWithFinalMethod {// 嘗試覆蓋final方法(編譯報錯)// @Override// public void finalMethod() { // 錯誤:final方法不能被覆蓋// System.out.println("嘗試覆蓋final方法");// }// 覆蓋普通方法(合法)@Overridepublic void normalMethod() {System.out.println("子類覆蓋了普通方法");}
}// 測試類
public class FinalMethodDemo {public static void main(String[] args) {ChildWithFinalMethod child = new ChildWithFinalMethod();child.finalMethod(); // 輸出:這是final方法,不能被覆蓋child.normalMethod(); // 輸出:子類覆蓋了普通方法}
}
7.3.3 final 修飾變量
final
修飾的變量只能被賦值一次(常量),賦值后不可修改。
特性:
- 局部變量:聲明時或構造器中賦值
- 成員變量:聲明時、構造塊或構造器中賦值(必須保證創建對象時已初始化)
- 引用類型變量:引用地址不可變,但對象內容可修改
代碼示例:final 變量
public class FinalVariableDemo {// 成員常量:聲明時賦值public static final double PI = 3.14159; // 靜態常量(通常全大寫)private final String name; // 實例常量// 構造塊中初始化final變量(可選){// name = "默認名稱"; // 若此處賦值,構造器中不可再賦值}// 構造器中初始化final變量public FinalVariableDemo(String name) {this.name = name; // 必須賦值,否則編譯報錯}public void showFinalVars() {// 局部final變量final int MAX_COUNT;MAX_COUNT = 100; // 第一次賦值(合法)// MAX_COUNT = 200; // 錯誤:final變量不能重復賦值System.out.println("PI:" + PI);System.out.println("name:" + name);System.out.println("MAX_COUNT:" + MAX_COUNT);}public void modifyFinalObject() {// final引用類型變量final StringBuilder sb = new StringBuilder("final引用");sb.append(",但內容可修改"); // 合法:對象內容可改System.out.println(sb.toString()); // 輸出:final引用,但內容可修改// sb = new StringBuilder("新對象"); // 錯誤:引用地址不可改}public static void main(String[] args) {FinalVariableDemo demo = new FinalVariableDemo("測試");demo.showFinalVars();demo.modifyFinalObject();}
}
7.4 抽象類
????????抽象類(abstract
?class)是包含抽象方法的類,它不能被實例化,只能作為父類被繼承。抽象類用于定義通用模板,強制子類實現特定方法。
核心特性:
- 用
abstract
關鍵字修飾 - 可包含抽象方法(無實現的方法)和具體方法(有實現)
- 子類必須實現所有抽象方法,否則子類也必須是抽象類
- 不能用
final
修飾(抽象類必須能被繼承)
類圖:抽象類與子類關系
@startuml
abstract class Shape {+ abstract double calculateArea()+ abstract double calculatePerimeter()+ void printInfo()
}class Circle {- double radius+ Circle(double radius)+ double calculateArea()+ double calculatePerimeter()
}class Rectangle {- double length- double width+ Rectangle(double length, double width)+ double calculateArea()+ double calculatePerimeter()
}Shape <|-- Circle
Shape <|-- Rectangle
@enduml
代碼示例:抽象類應用
// 抽象類:形狀(定義通用模板)
abstract class Shape {// 抽象方法:計算面積(無實現,由子類實現)public abstract double calculateArea();// 抽象方法:計算周長public abstract double calculatePerimeter();// 具體方法:打印形狀信息(已有實現)public void printInfo() {System.out.println("面積:" + calculateArea() + ",周長:" + calculatePerimeter());}
}// 子類:圓形(必須實現所有抽象方法)
class Circle extends Shape {private double radius; // 半徑public Circle(double radius) {this.radius = radius;}// 實現抽象方法:計算面積@Overridepublic double calculateArea() {return Math.PI * radius * radius;}// 實現抽象方法:計算周長@Overridepublic double calculatePerimeter() {return 2 * Math.PI * radius;}
}// 子類:矩形
class Rectangle extends Shape {private double length; // 長private double width; // 寬public Rectangle(double length, double width) {this.length = length;this.width = width;}@Overridepublic double calculateArea() {return length * width;}@Overridepublic double calculatePerimeter() {return 2 * (length + width);}
}// 測試類
public class AbstractClassDemo {public static void main(String[] args) {// 抽象類不能實例化// Shape shape = new Shape(); // 錯誤:Shape是抽象的,無法實例化// 創建子類對象,用父類引用接收Shape circle = new Circle(5);System.out.println("圓形信息:");circle.printInfo(); // 調用抽象類的具體方法,實際執行子類實現Shape rectangle = new Rectangle(4, 6);System.out.println("\n矩形信息:");rectangle.printInfo();}
}
運行結果:
7.5 對象轉換與多態
????????對象轉換和多態是實現靈活編程的關鍵,它們允許我們用統一的方式處理不同類型的對象。
7.5.1 對象轉換
對象轉換分為向上轉型和向下轉型:
- 向上轉型(自動轉換):子類對象 → 父類引用(
Parent p = new Child();
) - 向下轉型(強制轉換):父類引用 → 子類對象(
Child c = (Child)p;
,需確保安全性)
流程圖:對象轉換流程
代碼示例:對象轉換
// 父類:Animal
class Animal {public void eat() {System.out.println("動物吃東西");}
}// 子類:Dog
class Dog extends Animal {@Overridepublic void eat() {System.out.println("狗吃骨頭");}// 子類特有方法public void bark() {System.out.println("狗汪汪叫");}
}// 子類:Cat
class Cat extends Animal {@Overridepublic void eat() {System.out.println("貓吃魚");}// 子類特有方法public void meow() {System.out.println("貓喵喵叫");}
}// 測試類
public class ObjectCastDemo {public static void main(String[] args) {// 1. 向上轉型(自動)Animal animal = new Dog(); // animal引用指向Dog對象animal.eat(); // 輸出:狗吃骨頭(多態)// 2. 嘗試直接調用子類特有方法(編譯報錯)// animal.bark(); // 錯誤:Animal類沒有bark()方法// 3. 向下轉型(強制)if (animal instanceof Dog) { // 先判斷類型(安全)Dog dog = (Dog) animal; // 強制轉換dog.bark(); // 輸出:狗汪汪叫(調用子類特有方法)}// 4. 錯誤的向下轉型(運行時異常)Animal animal2 = new Cat();// Dog dog2 = (Dog) animal2; // 編譯通過,但運行時拋出ClassCastException}
}
7.5.2 instanceof 運算符
instanceof
用于判斷對象的實際類型,返回boolean
值,語法:對象 instanceof 類型
。
用途:
- 向下轉型前檢查類型,避免
ClassCastException
- 判斷對象是否屬于某個類或其子類
代碼示例:instanceof 使用
// 復用上面的Animal、Dog、Cat類public class InstanceOfDemo {public static void main(String[] args) {Animal animal = new Dog();// 判斷animal是否是Dog類型System.out.println(animal instanceof Dog); // true// 判斷animal是否是Animal類型(父類)System.out.println(animal instanceof Animal); // true// 判斷animal是否是Cat類型System.out.println(animal instanceof Cat); // false// 空對象的instanceof判斷Animal nullAnimal = null;System.out.println(nullAnimal instanceof Animal); // false(空對象返回false)// 安全的向下轉型if (animal instanceof Dog) {Dog dog = (Dog) animal;dog.bark(); // 安全調用}}
}
7.5.3 多態與動態綁定
????????多態(Polymorphism)指同一操作作用于不同對象,產生不同結果。表現為:父類引用指向子類對象,調用方法時實際執行子類的實現。
????????動態綁定(Dynamic Binding):程序運行時,JVM 根據對象的實際類型確定調用哪個方法的過程(而非引用類型)。
多態的條件:
- 存在繼承關系
- 子類覆蓋父類方法
- 父類引用指向子類對象
代碼示例:多態與動態綁定
// 父類:Shape
class Shape {public void draw() {System.out.println("繪制形狀");}
}// 子類:Circle
class Circle extends Shape {@Overridepublic void draw() {System.out.println("繪制圓形");}
}// 子類:Rectangle
class Rectangle extends Shape {@Overridepublic void draw() {System.out.println("繪制矩形");}
}// 工具類:繪圖工具(多態應用)
class DrawingTool {// 接收父類引用,實現通用繪圖方法public static void drawShape(Shape shape) {shape.draw(); // 動態綁定:調用實際類型的draw()}
}// 測試類
public class PolymorphismDemo {public static void main(String[] args) {// 父類引用指向不同子類對象Shape circle = new Circle();Shape rectangle = new Rectangle();// 多態:同一方法調用,不同結果DrawingTool.drawShape(circle); // 輸出:繪制圓形DrawingTool.drawShape(rectangle); // 輸出:繪制矩形}
}
運行結果:
7.5 綜合案例:多態計算器
需求:設計一個計算器,支持整數、小數和字符串拼接的加法運算,用多態實現統一調用接口。
完整代碼
// 抽象父類:計算器操作
abstract class Calculator {// 抽象方法:計算(子類實現不同類型的計算)public abstract Object calculate(Object a, Object b);
}// 子類:整數計算器
class IntegerCalculator extends Calculator {@Overridepublic Object calculate(Object a, Object b) {// 類型檢查if (!(a instanceof Integer) || !(b instanceof Integer)) {throw new IllegalArgumentException("整數計算器只支持Integer類型");}// 強轉并計算int num1 = (Integer) a;int num2 = (Integer) b;return num1 + num2;}
}// 子類:小數計算器
class DoubleCalculator extends Calculator {@Overridepublic Object calculate(Object a, Object b) {// 支持Integer和Double混合計算double num1 = (a instanceof Integer) ? (Integer) a : (Double) a;double num2 = (b instanceof Integer) ? (Integer) b : (Double) b;return num1 + num2;}
}// 子類:字符串計算器(拼接)
class StringCalculator extends Calculator {@Overridepublic Object calculate(Object a, Object b) {// 任何類型都轉為字符串拼接return a.toString() + b.toString();}
}// 測試類
public class PolymorphismCalculatorDemo {public static void main(String[] args) {// 創建不同計算器(多態數組)Calculator[] calculators = {new IntegerCalculator(),new DoubleCalculator(),new StringCalculator()};// 測試整數計算System.out.println("整數計算:10 + 20 = " + calculators[0].calculate(10, 20));// 測試小數計算System.out.println("小數計算:3.5 + 4.8 = " + calculators[1].calculate(3.5, 4.8));System.out.println("混和計算:5 + 3.2 = " + calculators[1].calculate(5, 3.2));// 測試字符串拼接System.out.println("字符串拼接:\"Hello\" + \"World\" = " + calculators[2].calculate("Hello", "World"));System.out.println("對象拼接:123 + true = " + calculators[2].calculate(123, true));}
}
運行結果:
7.6 小結
本章重點講解了 Java 的繼承與多態特性,核心知識點包括:
- 繼承:通過
extends
實現代碼復用,子類繼承父類的屬性和方法,可通過super
訪問父類資源 - 方法覆蓋:子類重寫父類方法,需遵循方法簽名、權限等規則,用
@Override
注解標識 - 封裝與訪問修飾符:通過
public
/protected
/default
/private
控制訪問權限,實現數據安全 - final 關鍵字:用于修飾類(不可繼承)、方法(不可覆蓋)、變量(常量)
- 抽象類:含抽象方法的類,強制子類實現特定方法,作為通用模板使用
- 對象轉換:向上轉型(自動)和向下轉型(強制),
instanceof
用于類型檢查 - 多態與動態綁定:父類引用指向子類對象,運行時調用實際類型的方法,實現靈活編程
編程練習
練習 1:繼承與方法覆蓋
????????需求:設計Person
類作為父類,包含name
和age
屬性及introduce()
方法;Student
類繼承Person
,新增studentId
屬性,并重寫introduce()
方法;Teacher
類繼承Person
,新增subject
屬性,并重寫introduce()
方法。
參考答案:
// Person類
class Person {protected String name;protected int age;public Person(String name, int age) {this.name = name;this.age = age;}public void introduce() {System.out.println("大家好,我叫" + name + ",今年" + age + "歲");}
}// Student類
class Student extends Person {private String studentId;public Student(String name, int age, String studentId) {super(name, age);this.studentId = studentId;}@Overridepublic void introduce() {System.out.println("大家好,我叫" + name + ",今年" + age + "歲,學號是" + studentId);}
}// Teacher類
class Teacher extends Person {private String subject;public Teacher(String name, int age, String subject) {super(name, age);this.subject = subject;}@Overridepublic void introduce() {System.out.println("大家好,我叫" + name + ",今年" + age + "歲,教" + subject);}
}// 測試類
public class PersonDemo {public static void main(String[] args) {Person student = new Student("小明", 18, "2023001");Person teacher = new Teacher("李老師", 35, "數學");student.introduce(); // 輸出:大家好,我叫小明,今年18歲,學號是2023001teacher.introduce(); // 輸出:大家好,我叫李老師,今年35歲,教數學}
}
練習 2:多態應用
????????需求:設計抽象類Vehicle
,包含抽象方法run()
;Car
、Bicycle
、Train
類繼承Vehicle
并實現run()
;創建工具類TrafficTool
,用多態方法start(Vehicle vehicle)
調用不同交通工具的運行方法。
參考答案:
// 抽象類:交通工具
abstract class Vehicle {public abstract void run();
}// 汽車
class Car extends Vehicle {@Overridepublic void run() {System.out.println("汽車在公路上行駛");}
}// 自行車
class Bicycle extends Vehicle {@Overridepublic void run() {System.out.println("自行車在自行車道騎行");}
}// 火車
class Train extends Vehicle {@Overridepublic void run() {System.out.println("火車在鐵軌上行駛");}
}// 工具類
class TrafficTool {public static void start(Vehicle vehicle) {System.out.print("交通工具啟動:");vehicle.run(); // 多態調用}
}// 測試類
public class VehicleDemo {public static void main(String[] args) {Vehicle car = new Car();Vehicle bicycle = new Bicycle();Vehicle train = new Train();TrafficTool.start(car); // 輸出:交通工具啟動:汽車在公路上行駛TrafficTool.start(bicycle); // 輸出:交通工具啟動:自行車在自行車道騎行TrafficTool.start(train); // 輸出:交通工具啟動:火車在鐵軌上行駛}
}
練習 3:抽象類與 final 綜合應用
????????需求:設計抽象類BankAccount
(銀行賬戶),包含抽象方法calculateInterest()
(計算利息);SavingsAccount
(儲蓄賬戶)和CurrentAccount
(活期賬戶)繼承并實現利息計算;用final
修飾BankAccount
的accountNumber
屬性(賬號不可修改)。
參考答案:
// 抽象類:銀行賬戶
abstract class BankAccount {// 賬號:final修飾,不可修改private final String accountNumber;protected double balance; // 余額public BankAccount(String accountNumber, double balance) {this.accountNumber = accountNumber;this.balance = balance;}// 抽象方法:計算利息public abstract double calculateInterest();// 獲取賬號(只提供getter,無setter)public String getAccountNumber() {return accountNumber;}// 存款public void deposit(double amount) {if (amount > 0) {balance += amount;System.out.println("存款" + amount + "元,當前余額:" + balance);}}// 顯示賬戶信息public void showAccountInfo() {System.out.println("賬號:" + accountNumber + ",余額:" + balance + "元,利息:" + calculateInterest() + "元");}
}// 儲蓄賬戶(利息3%)
class SavingsAccount extends BankAccount {public SavingsAccount(String accountNumber, double balance) {super(accountNumber, balance);}@Overridepublic double calculateInterest() {return balance * 0.03; // 年利率3%}
}// 活期賬戶(利息0.3%)
class CurrentAccount extends BankAccount {public CurrentAccount(String accountNumber, double balance) {super(accountNumber, balance);}@Overridepublic double calculateInterest() {return balance * 0.003; // 年利率0.3%}
}// 測試類
public class BankAccountDemo {public static void main(String[] args) {BankAccount savings = new SavingsAccount("SA2023001", 10000);BankAccount current = new CurrentAccount("CA2023001", 5000);savings.deposit(2000);current.deposit(1000);savings.showAccountInfo(); // 輸出:賬號:SA2023001,余額:12000.0元,利息:360.0元current.showAccountInfo(); // 輸出:賬號:CA2023001,余額:6000.0元,利息:18.0元}
}
????????希望通過本章內容,你能徹底掌握 Java 繼承與多態的核心概念和實踐技巧。如果有任何問題或代碼運行問題,歡迎在評論區交流!