同專欄基礎知識篇寫在這里,有興趣的可以去看看:
編程語言Java入門——基礎知識篇(一)-CSDN博客
編程語言Java入門——基礎知識篇(二)-CSDN博客
編程語言Java入門——基礎知識篇(三)類和對象-CSDN博客
編程語言Java入門——基礎知識篇(四)包裝類、數字處理類-CSDN博客
目錄
1. 接口、繼承與多態
1.1 接口(implement)
1.1.1 接口的本質
1.1.2?接口的核心特性
1.1.3 接口的應用場景
1.2 類的繼承(extends)
1.2.1 繼承的基本概念
1.2.2 繼承的核心特性
1.2.3 構造方法的繼承?
1.2.4 繼承的高級特性
1.3 重寫 Overload
1.3.1 方法重寫基本概念
1.3.2 重寫的嚴格規則
1.3.3 重寫的高級特性與細節
1.3.4 特殊場景處理
1.4 Object類
1.4.1 Object類概述
1.4.2 核心方法詳解
1.5 instanceof 判斷對象類型
1.5.1 基本語法與語義
1.5.2?核心特性
1.5.3?高級用法
1.5.4 典型應用場景
1.6 重載Overload
1.6.1 基本概念與語法
1.6.2 重載的核心規則
1.6.3 方法解析機制
1.6.4 高級特性?
1.6.5 Overload與Override對比
1.7 多態(Polymorphism)
1.7.1 多態的基本概念
1.7.2 多態的實現形式
1.7.3 多態的實現機制
1.7.4 多態的高級應用
1.7.5 多態的使用技巧
1.8 小結
1. 接口、繼承與多態
1.1 接口(implement)
1.1.1 接口的本質
接口(Interface)是面向對象編程中的一個重要概念,它定義了一組方法簽名而不包含實現。它是一種完全抽象的"類模板"。在Java 8之后,接口可以包含默認方法和靜態方法實現,但其核心目的仍然是定義契約。
1.1.2?接口的核心特性
1. 基本特性:
(1) 抽象性
只聲明方法,不提供實現:接口僅定義方法的簽名(名稱、參數、返回類型),不包含方法體,具體實現由實現類完成。
關注"做什么",而非"怎么做":接口定義了一組行為規范,強調功能的抽象描述,而不關心具體的實現細節。
不能實例化:接口本身不能被直接創建對象,必須通過實現類來使用。
(2) 契約性
強制實現類提供方法:任何類實現接口時,必須實現接口中聲明的所有抽象方法(除非是抽象類)。
標準化交互方式:接口規定了類之間的通信協議,確保不同的類可以按照統一的方式被調用。
增強代碼可維護性:通過接口約束,可以更容易地替換不同的實現,而不會影響調用方代碼。
(3) 多態支持
同一接口,不同實現:不同的類可以實現同一個接口,并通過接口類型引用調用,實現運行時多態。
提高代碼靈活性:例如,List 接口可以有 ArrayList 和 LinkedList 兩種實現,但調用方式一致。
適用于策略模式、依賴注入等:接口多態是許多設計模式(如工廠模式、觀察者模式)的基礎。
2. 技術特性
- 方法聲明
(1)抽象方法(默認):
接口中的方法默認是?public abstract
,無需顯式聲明。
例如:void fly();
?就是一個抽象方法。
interface Swimmer {void swim();
}interface Flyer {void fly();
}class Duck implements Swimmer, Flyer { // 實現多個接口public void swim() { /*...*/ }public void fly() { /*...*/ }
}
這里面我們定義兩個接口分別來代表鴨子的兩種行為——游泳 swim 和飛 fly 。那么當我們定義一個Duck類時用關鍵字 implements 來繼承接口。當繼承接口以后,接口內的方法就必須實現。
(2)默認方法(Java 8+):
使用?default
?關鍵字,提供默認實現,實現類可選擇重寫或直接使用。
主要用于接口演化,避免破壞已有代碼。例如:
default void log(String message) {System.out.println("Log: " + message);
}
(3)靜態方法(Java 8+):
使用?static
?關鍵字定義,屬于接口本身,不能被子類繼承或重寫。
通常用于工具方法,例如:
static boolean isValid(String input) {return input != null && !input.isEmpty();
}
(4)私有方法(Java 9+):(這個僅作了解,因為我用的時Java8)
用于在接口內部復用代碼,避免默認方法重復邏輯。
只能被接口內的其他方法調用。
- 變量聲明
(1)自動為?public static final
:
接口中的變量默認是常量,即使不寫修飾符也會自動加上?public static final
。
例如:int MAX_COUNT = 100;
?等同于?public static final int MAX_COUNT = 100;
。
(2)主要用于配置或全局常量:
例如定義錯誤碼、默認配置等。
- 繼承關系
(1)接口可以繼承多個接口(多重繼承):
例如:
interface A { void methodA(); }
interface B { void methodB(); }
interface C extends A, B { void methodC(); }
實現?C
?的類必須實現?methodA()
、methodB()
?和?methodC()
。
(2)類可以實現多個接口:
例如:class MyClass implements A, B, C { ... }
。
- 訪問修飾符
(1)方法默認?public
:
即使不寫?public
,接口方法仍然是公開的,不能是?private
?或?protected
(私有方法除外)。
(2)接口本身可以是?public
?或包私有:
如果不加?public
,則接口僅在當前包內可見。
3. 高級特性
(1) 默認方法(Default Methods)
-
解決接口演化問題:
-
在 Java 8 之前,如果給接口新增方法,所有實現類都必須修改,否則編譯失敗。
-
默認方法允許接口提供默認實現,避免破壞已有代碼。
-
-
可以被重寫:
-
實現類可以選擇使用默認實現,也可以覆蓋它。
-
-
沖突處理:
-
如果類實現了兩個接口,且它們有相同的默認方法,必須顯式重寫,否則編譯錯誤。
-
(2) 靜態方法(Static Methods)
-
屬于接口本身:
-
通過接口名直接調用,例如:
MyInterface.staticMethod()
。
-
-
不能被子類繼承或重寫:
-
即使實現類定義了同名靜態方法,也不會覆蓋接口的靜態方法。
-
(3) 函數式接口(Functional Interface)
-
僅含一個抽象方法:
-
例如?
Runnable
(僅?run()
)、Comparator
(僅?compare()
)。
-
-
可用 Lambda 表達式實現:
-
例如:
Runnable r = () -> System.out.println("Hello");
。
-
-
可加?
@FunctionalInterface
?注解:-
用于編譯器檢查,確保接口符合函數式接口規范。
-
(4) 標記接口(Marker Interface)
-
無任何方法:
-
例如?
Serializable
、Cloneable
,僅用于標記類具有某種能力。
-
-
通過反射或類型檢查使用:
-
例如?
if (obj instanceof Serializable) { ... }
。
-
1.1.3 接口的應用場景
1. 定義通用行為(如?Comparable
、Runnable
)。
2. 實現回調機制(如事件監聽器?EventListener
)。
3. 依賴注入(Spring 框架中廣泛使用接口解耦)。
4. 策略模式(通過不同實現類切換算法)。
5. API 設計(如 JDBC 的?Connection
、Statement
?接口)。
1.2 類的繼承(extends)
1.2.1 繼承的基本概念
繼承是面向對象編程中最重要的特性之一,它允許新建類基于現有類進行構建。現有類稱為父類/超類/基類,新建類稱為子類/派生類。
生物學類比:
就像"貓"繼承自動物類,具有動物的所有基本特征(呼吸、進食等),同時又有自己特有的行為(喵喵叫、抓老鼠)。
其語法形式為:
// 基礎語法
class ParentClass {
? ? // 父類成員
}class ChildClass extends ParentClass { ?// 使用extends關鍵字
? ? // 可以添加新成員
? ? // 可以重寫父類方法
}
1.2.2 繼承的核心特性
1. 成員繼承
子類自動獲得父類的非私有成員(字段和方法),包括:
-
public和protected修飾的成員
-
默認訪問修飾符(包私有)的同包成員
-
不包括private成員和構造方法
class Vehicle {protected String brand = "Ford";public void honk() {System.out.println("Tuut, tuut!");}
}class Car extends Vehicle {private String modelName = "Mustang";public static void main(String[] args) {Car myCar = new Car();myCar.honk(); // 繼承自VehicleSystem.out.println(myCar.brand + " " + myCar.modelName);}
}
?2. super關鍵字
用于訪問父類的成員,有四種使用方式:
- 調用父類構造方法(必須放在子類構造方法的第一行):
class Parent {Parent(int x) { /*...*/ }
}class Child extends Parent {Child(int x, int y) {super(x); // 調用父類構造// 子類初始化代碼}
}
- 訪問父類被隱藏的字段:
class Parent {String name = "Parent";
}class Child extends Parent {String name = "Child";void printNames() {System.out.println(super.name); // 輸出ParentSystem.out.println(this.name); // 輸出Child}
}
- 調用父類被重寫的方法:
@Override
public void someMethod() {super.someMethod(); // 先執行父類邏輯// 添加子類特有邏輯
}
1.2.3 構造方法的繼承?
1. 創建子類對象時,構造方法的調用順序:
-
父類靜態代碼塊(首次加載時)
-
子類靜態代碼塊(首次加載時)
-
父類實例代碼塊
-
父類構造方法
-
子類實例代碼塊
-
子類構造方法
class Parent {static { System.out.println("Parent靜態塊"); }{ System.out.println("Parent實例塊"); }Parent() { System.out.println("Parent構造"); }
}class Child extends Parent {static { System.out.println("Child靜態塊"); }{ System.out.println("Child實例塊"); }Child() { // 隱含super();System.out.println("Child構造"); }
}// 輸出順序:
// Parent靜態塊
// Child靜態塊
// Parent實例塊
// Parent構造
// Child實例塊
// Child構造
2.?構造方法注意事項
-
隱式調用:如果子類構造方法沒有顯式調用super()或this(),編譯器會自動插入super()
-
必須第一行:super()或this()調用必須是構造方法的第一條語句
-
無默認構造:如果父類沒有無參構造,子類必須顯式調用super(參數)
1.2.4 繼承的高級特性
1. 這里再強調一下Java四種修飾符再繼承中的表現(基礎篇(一)提到過):
修飾符 | 同類 | 同包 | 子類 | 不同包 |
---|---|---|---|---|
private | ? | ? | ? | ? |
default | ? | ? | ?* | ? |
protected | ? | ? | ? | ? |
public | ? | ? | ? | ? |
2. final關鍵字
-
final類:不能被繼承
java
final class FinalClass { /*...*/ } // class Child extends FinalClass {} // 編譯錯誤
-
final方法:不能被子類重寫
class Parent {final void finalMethod() { /*...*/ } }class Child extends Parent {// void finalMethod() {} // 編譯錯誤 }
-
final變量:基本類型值不可變,引用類型引用不可變
final int x = 10; // x = 20; // 編譯錯誤final List<String> list = new ArrayList<>(); list.add("item"); // 允許 // list = new LinkedList<>(); // 編譯錯誤
1.3 重寫 Overload
1.3.1 方法重寫基本概念
方法重寫(Override)是面向對象編程中子類重新定義父類已有方法的行為,也稱為方法覆蓋。這是實現運行時多態(動態綁定)的關鍵機制。
核心特點:
-
發生在繼承關系的子類中
-
方法簽名(名稱+參數列表)必須完全相同
-
子類方法提供新的實現邏輯
-
通過父類引用調用時,實際執行子類的方法
class Animal {public void makeSound() {System.out.println("動物發出聲音");}
}class Cat extends Animal {@Override // 注解明確表示這是重寫public void makeSound() {System.out.println("喵喵叫");}
}// 使用
Animal myCat = new Cat();
myCat.makeSound(); // 輸出"喵喵叫"(動態綁定)
1.3.2 重寫的嚴格規則
1. 方法簽名一致性
必須完全匹配以下要素:
-
方法名完全相同
-
參數列表(類型、數量、順序)完全相同
-
返回類型可以是原返回類型的子類(協變返回)
class Parent {Number getNumber() { return 0; }
}class Child extends Parent {@OverrideInteger getNumber() { return 42; } // Integer是Number的子類
}
2. 訪問權限的規則
子類方法的訪問權限不能比父類更嚴格,但可以更寬松:
父類方法訪問權限 | 允許的子類方法權限 |
---|---|
private | 不能重寫 |
默認(package) | protected, public |
protected | protected, public |
public | 只能是public |
這里放一個錯誤示例:?
class Parent {public void method() {}
}class Child extends Parent {@Overridevoid method() {} // 編譯錯誤:不能降低訪問權限
}
1.3.3 重寫的高級特性與細節
1.?@Override注解
-
作用:顯式聲明這是重寫方法
-
好處:
-
編譯器會檢查是否真的重寫了父類方法
-
提高代碼可讀性
-
防止因拼寫錯誤導致意外創建新方法
-
重要示例:
class Parent {void doWork() {}
}class Child extends Parent {@Overridevoid dooWork() {}? // 編譯錯誤:沒有真正重寫// 沒有@Override時,dooWork()會被當作新方法,導致邏輯錯誤
}
2.?靜態方法"重寫"
靜態方法不能被重寫,只能被隱藏(看起來類似但機制不同):
class Parent {static void staticMethod() {System.out.println("Parent static");}
}class Child extends Parent {static void staticMethod() {? // 這是方法隱藏,不是重寫System.out.println("Child static");}
}
// 測試
Parent p = new Child();
p.staticMethod();? // 輸出"Parent static"(靜態綁定)
3.?私有方法重寫
私有方法不能被重寫,子類中定義同名方法實際上是新方法:
class Parent {private void privateMethod() {System.out.println("Parent private");}void callPrivate() {privateMethod(); // 總是調用Parent的實現}
}class Child extends Parent {// 這是全新的方法,不是重寫private void privateMethod() {System.out.println("Child private");}
}// 測試
Child c = new Child();
c.callPrivate(); // 輸出"Parent private"
1.3.4 特殊場景處理
1.?重寫equals和hashCode
當重寫equals時必須同時重寫hashCode,遵守通用契約:
class Person {private String id;@Overridepublic boolean equals(Object o) {if (this == o) return true;// instanceof檢查對象是否是指定類型或其子類型的示例if (!(o instanceof Person)) return false; Person p = (Person) o;return id.equals(p.id);}@Overridepublic int hashCode() {return id.hashCode();? // 必須與equals一致}
}
2. 重寫finalize方法(注:Java 9后finalize已被廢棄,此處僅為演示)
@Override
protected void finalize() throws Throwable {try {// 清理資源} finally {super.finalize();? // 必須調用父類實現}
}
1.4 Object類
Object 類是 Java 所有類的超類(父類),位于 java.lang 包中。每個 Java 類都直接或間接繼承自 Object 類,它提供了所有對象的基本行為。
1.4.1 Object類概述
1. 核心地位
-
所有類的根父類(沒有顯式繼承的類自動繼承 Object)
-
數組類型也是 Object 的子類
-
泛型中的類型參數最終都會擦除到 Object
2. 類定義
// 以下為源碼
public class Object {// 原生方法(由JVM實現)private static native void registerNatives();static {registerNatives();}// 其他方法...
}
1.4.2 核心方法詳解
1. toString() 方法
作用:返回對象的字符串表示形式
默認實現:
public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
最佳實踐:
-
所有可實例化的類都應重寫此方法
-
應返回簡潔但信息豐富的表示
-
格式建議:
ClassName[field1=value1, field2=value2]
@Override
public String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';
}
在不重寫toString() 方法前,使用該方法均返回該對象的地址。上面的例子改寫后則返回字符串。
2. equals() 與 hashCode()
(1)equals() 方法
契約:
-
自反性:
x.equals(x)
?必須返回 true -
對稱性:
x.equals(y)
?與?y.equals(x)
?結果相同 -
傳遞性:如果?
x.equals(y)
?且?y.equals(z)
,則?x.equals(z)
-
一致性:多次調用結果不變(除非對象被修改)
-
非空性:
x.equals(null)
?必須返回 false
重寫步驟:
-
檢查是否同一引用
-
檢查是否為 null
-
檢查是否同類(考慮繼承時使用 getClass() 或 instanceof)
-
強制類型轉換
-
比較關鍵字段
示例:
@Override
public boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return age == person.age && Objects.equals(name, person.name);
}
(2)hashCode() 方法
契約:
-
一致性:對象未修改時多次調用返回相同值
-
相等對象必須產生相同哈希碼
-
不等對象最好產生不同哈希碼(但不是必須)
重寫建議:
-
使用相同的字段計算
-
使用?
java.util.Objects.hash()
?工具方法
@Override
public int hashCode() {return Objects.hash(name, age);
}
ps:為什么重寫 equals() 必須重寫 hashCode()?
Java 對象契約明確規定:
-
如果?
obj1.equals(obj2)
?為?true
,則?obj1.hashCode()
?必須等于?obj2.hashCode()
-
但?
hashCode()
?相等的對象不一定?equals()
?為?true
哈希集合異常示例:
class Person {String name;// 只重寫equals沒重寫hashCode@Overridepublic boolean equals(Object o) {//... 實現比較邏輯}
}Person p1 = new Person("Alice");
Person p2 = new Person("Alice");Set<Person> set = new HashSet<>();
set.add(p1);
set.contains(p2); // 可能返回false,違反預期
3. clone() 方法
作用:創建并返回對象的副本
特點:
-
必須實現?
Cloneable
?接口(標記接口) -
默認實現是淺拷貝
-
通常應重寫為深拷貝
@Override
protected Object clone() throws CloneNotSupportedException {Person cloned = (Person) super.clone();cloned.birthDate = (Date) birthDate.clone(); // 深拷貝return cloned;
}
ps:如何實現深拷貝?
特性 | 淺拷貝 | 深拷貝 |
---|---|---|
基本類型字段 | 值復制 | 值復制 |
引用類型字段 | 復制引用(共享對象) | 遞歸創建新對象 |
修改影響 | 影響原對象 | 不影響原對象 |
實現復雜度 | 簡單(默認clone行為) | 復雜(需遞歸處理) |
?
方式1:Cloneable 接口
步驟:
-
實現?
Cloneable
?標記接口 -
重寫?
clone()
?方法(提升為public) -
對每個引用字段遞歸調用?
clone()
示例:
class Department implements Cloneable {String name;Employee manager;@Overridepublic Department clone() {try {Department cloned = (Department) super.clone();cloned.manager = this.manager.clone(); // 深拷貝關鍵return cloned;} catch (CloneNotSupportedException e) {throw new AssertionError(); // 不可能發生}}
}
缺點:
-
需要所有引用類型都支持克隆
-
容易遺漏深層字段
方式2:序列化法
原理:通過對象序列化/反序列化實現完全復制
示例:
import java.io.*;public class SerializationUtils {public static <T extends Serializable> T deepCopy(T object) {try {ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(object);ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bais);return (T) ois.readObject();} catch (IOException | ClassNotFoundException e) {throw new RuntimeException(e);}}
}
優點:自動處理整個對象圖;不需要每個類單獨實現
缺點:性能較低;所有相關類必須實現?Serializable
方式3:復制構造器/工廠
示例:
class Project {private String title;private List<Task> tasks;// 復制構造器public Project(Project other) {this.title = other.title;this.tasks = new ArrayList<>();for (Task task : other.tasks) {this.tasks.add(new Task(task)); // 遞歸深拷貝}}
}
優點:代碼控制力強;不需要實現特殊接口
缺點:需要為每個類編寫復制邏輯;維護成本高
深拷貝方式選擇:
-
優先選擇不可變對象:避免拷貝需求
-
簡單對象用Cloneable:結構簡單時適用
-
復雜對象用序列化:對象圖復雜時推薦
-
性能敏感場景用構造器:需要最高性能時使用
-
使用第三方庫:如Apache Commons Lang的
SerializationUtils
4. getClass() 方法
作用:返回對象的運行時類對象
特點:
-
final 方法,不能重寫
-
返回?
Class<?>
?對象 -
可用于反射
// 中括號內是泛型的知識,后面會講,這里僅做一個延申示例
Class<?> clazz = obj.getClass();
String className = clazz.getName();
?
1.5 instanceof 判斷對象類型
instanceof
?是 Java 中的一個二元運算符,用于檢查對象是否是指定類型或其子類型的實例。它是 Java 類型系統的重要組成部分,廣泛應用于類型檢查和類型轉換場景。
1.5.1 基本語法與語義
1. 語法形式
object instanceof Type
-
object
:要檢查的對象引用 -
Type
:類/接口類型或數組類型 -
返回?
boolean
?值:true
?表示對象是指定類型或其子類型的實例
2.?基本示例
// 超類的引用指向子類的繼承
Object obj = new ArrayList<>();
// ArrayList可以理解成一個動態的數組,但因為有泛型所以可以在里面裝任何對象
// 比如我裝浮點型數字,整型數字,字符串等System.out.println(obj instanceof List);?? // true
System.out.println(obj instanceof ArrayList); // true
System.out.println(obj instanceof String);??? // false
// instanceof是判斷obj類型的運算符,而ArrayList繼承List,所以返回true
1.5.2?核心特性
1. 類型檢查范圍
instanceof
?檢查對象是否屬于以下情況:
-
指定類或其子類的實例
-
指定接口的實現類的實例
-
指定數組類型或其子類型的實例
Number num = Integer.valueOf(10);System.out.println(num instanceof Number);? // true (相同類)
System.out.println(num instanceof Integer); // true (子類)
System.out.println(num instanceof Comparable); // true (實現接口)
2. null 處理
instanceof
?對?null
?值總是返回?false
:
String str = null;
System.out.println(str instanceof String);? // false
3. 編譯時檢查
編譯器會進行靜態類型檢查,阻止明顯不可能成立的比較:
Integer i = 10;
// System.out.println(i instanceof String);? // 編譯錯誤:不兼容的類型
1.5.3?高級用法
1. 泛型類型檢查
由于類型擦除,instanceof
?不能檢查具體的泛型類型參數:
List<String> stringList = new ArrayList<>();System.out.println(stringList instanceof List);?????? // true
// System.out.println(stringList instanceof List<String>);? // 編譯錯誤
System.out.println(stringList instanceof List<?>);?? // true
2. 數組類型檢查
可以檢查數組類型及其維度:
int[] arr = new int[10];System.out.println(arr instanceof int[]);??? // true
System.out.println(arr instanceof Object);? // true (所有數組都是Object子類)
System.out.println(arr instanceof long[]);? // false
1.5.4 典型應用場景
1. 安全類型轉換
public void process(Object obj) {if (obj instanceof String) {String s = (String) obj;// 安全使用s}
}
2 多態方法處理
void handleShape(Shape shape) {if (shape instanceof Circle) {Circle c = (Circle) shape;c.drawCircle();} else if (shape instanceof Rectangle) {Rectangle r = (Rectangle) shape;r.drawRectangle();}
}
3.?equals方法實現
@Override
public boolean equals(Object obj) {if (this == obj) return true;if (!(obj instanceof MyClass)) return false;MyClass other = (MyClass) obj;// 比較字段...
}
1.5.5 使用性能與使用方法
1. 性能考慮
-
instanceof
?本身是高效操作(通常只是指針比較) -
過度使用可能表明設計問題(違反多態原則)
2. 最佳實踐
-
優先使用多態:能用多態解決的問題不要用?
instanceof
-
檢查順序:將更具體的類型檢查放在前面
-
模式匹配:Java 16+ 應使用模式匹配簡化代碼
-
防御性編程:在強制轉換前總是先檢查
3.?反例
// 不好的寫法:過度使用instanceof
void process(Object obj) {if (obj instanceof TypeA) {// 處理A} else if (obj instanceof TypeB) {// 處理B} // 更多else if...
}// 更好的設計:使用多態
interface Processor {void process();
}class TypeA implements Processor { /*...*/ }
class TypeB implements Processor { /*...*/ }
1.6 重載Overload
方法重載是Java中實現編譯時多態的重要機制,它允許在同一個類中定義多個同名方法,通過不同的參數列表來區分它們。
1.6.1 基本概念與語法
1. 什么是重載?
方法重載(Overloading)是指:在同一個類中定義了多個方法,名稱相同,但參數列表不同(類型、數量或順序)且與返回類型和訪問修飾符無關。
2. 基本語法形式
class Calculator {// 重載方法示例int add(int a, int b) { return a + b; }double add(double a, double b) { return a + b; }int add(int a, int b, int c) { return a + b + c; }
}
1.6.2 重載的核心規則
1.?合法重載的條件
必須滿足以下至少一項參數列表差異:
參數類型不同:
void print(int i) {}
void print(String s) {}
參數數量不同:
void log(String msg) {}
void log(String msg, int level) {}
參數順序不同(類型必須真正不同):
void process(int a, String b) {}
void process(String a, int b) {}
2.?不構成重載的情況
- 僅返回類型不同:
int getValue() {}
double getValue() {}? // 編譯錯誤
- 僅訪問修飾符不同:
public void execute() {}
private void execute() {}? // 編譯錯誤
- 僅參數名稱不同:(這個真的顯而易見)
void test(int a) {}
void test(int b) {}? // 編譯錯誤
- 僅拋出異常不同:
void parse() throws IOException {}
void parse() throws SQLException {} // 編譯錯誤
1.6.3 方法解析機制
1.?編譯時確定
Java編譯器在編譯階段就確定調用哪個重載方法,基于:
-
方法名稱
-
參數表達式類型
-
參數數量
2. 匹配優先級
當存在多個可能匹配時,按以下順序選擇:
-
精確匹配:參數類型完全一致
-
基本類型自動轉換:按
byte → short → int → long → float → double
順序 -
自動裝箱/拆箱
-
可變參數
3.?典型匹配過程
class OverloadDemo {void test(int a) { System.out.println("int"); }void test(Integer a) { System.out.println("Integer"); }void test(long a) { System.out.println("long"); }void test(Object a) { System.out.println("Object"); }void test(int... a) { System.out.println("varargs"); }
}// 調用示例
OverloadDemo demo = new OverloadDemo();
demo.test(5); // 輸出"int"(精確匹配)
demo.test(5L); // 輸出"long"
demo.test(5.0); // 輸出"Object"(自動裝箱Double)
demo.test(); // 輸出"varargs"
1.6.4 高級特性?
1.?可變參數重載
void process(String... strs) {}
void process(String first, String... rest) {}? // 合法但危險
?這個示例中可能對“合法但危險”這個說法感到疑惑。這里我們可以先從兩個角度來分析。
從Java語法角度來看,它是合法的,因為兩個方法的參數列表形式確實不同?,而且滿足方法重載的基本要求,至少在編譯時Java編譯器不會報錯。但如果我這么調用該用哪個方法?
process("hello"); // 該調用哪個方法?
編譯器無法確定:
-
可以匹配
process(String... strs)
,將"hello"作為可變參數的唯一元素 -
也可以匹配
process(String first, String... rest)
,將"hello"作為first參數,rest為空數組
再或者我這么寫,就直接會出現編譯錯誤。
// 都會產生"ambiguous method call"錯誤
process("a");
process("a", "b");
process("a", "b", "c");
?因此這個點要尤其注意。
2. 自動裝箱與重載
void execute(int num) {}
void execute(Integer num) {}execute(10);???? // 調用int版本
execute(new Integer(10));? // 調用Integer版本
3. 繼承中的重載
class Parent {void run(String input) {}
}class Child extends Parent {// 這是重載不是重寫!void run(int input) {}
}
4. 靜態方法重載
靜態方法也可以重載,且遵循相同規則:
class Utils {static void format(Date date) {}static void format(LocalDate date) {}
}
1.6.5 Overload與Override對比
特性 | 重載(Overload) | 重寫(Override) |
---|---|---|
發生位置 | 同一個類內 | 子類與父類之間 |
參數要求 | 必須不同 | 必須相同 |
返回類型 | 可以不同 | 必須相同或協變 |
訪問權限 | 可以不同 | 不能更嚴格 |
異常拋出 | 可以不同 | 不能更寬泛 |
綁定時機 | 編譯時靜態綁定 | 運行時動態綁定 |
多態類型 | 編譯時多態 | 運行時多態 |
1.7 多態(Polymorphism)
多態是面向對象編程的三大特性之一(封裝、繼承、多態),它允許不同類的對象對同一消息做出不同響應,極大地提高了代碼的靈活性和可擴展性。
1.7.1 多態的基本概念
1. 什么是多態?
多態(Polymorphism)指同一操作作用于不同的對象,可以產生不同的執行結果。在Java中主要表現為:
-
編譯時多態:方法重載(靜態綁定)
-
運行時多態:方法重寫(動態綁定)
2.?多態的必要條件:
-
繼承關系:存在父子類關系
-
方法重寫:子類重寫父類方法
-
向上轉型:父類引用指向子類對象
1.7.2 多態的實現形式
1. 編譯時多態(靜態多態)
通過方法重載實現,在編譯時確定調用哪個方法:
class Calculator {int add(int a, int b) { return a + b; }double add(double a, double b) { return a + b; } // 重載
}
2. 運行時多態(動態多態)
通過方法重寫實現,在運行時確定實際調用的方法:
class Animal {void sound() { System.out.println("動物發聲"); }
}class Dog extends Animal {@Overridevoid sound() { System.out.println("汪汪叫"); } // 重寫
}Animal myDog = new Dog(); // 向上轉型
myDog.sound(); // 輸出"汪汪叫"(運行時確定)
1.7.3 多態的實現機制
1 JVM方法調用原理:
-
靜態綁定:編譯時確定(private/static/final方法和構造方法)
-
動態綁定:基于對象實際類型確定(普通實例方法)
2. 虛方法表(VTable)
JVM通過虛方法表實現動態綁定:
-
每個類有一個方法表
-
包含該類所有可重寫方法的實際入口地址
-
調用時根據對象實際類型查找方法表
1.7.4 多態的高級應用
1.?接口多態
interface USB {void transfer();
}class Mouse implements USB {public void transfer() { System.out.println("鼠標數據傳輸"); }
}class Keyboard implements USB {public void transfer() { System.out.println("鍵盤數據傳輸"); }
}// 使用
USB device = new Mouse();
device.transfer(); // 多態調用
2. 多態參數
void drawShape(Shape shape) { // 接受任何Shape子類shape.draw();
}drawShape(new Circle()); // 繪制圓形
drawShape(new Square()); // 繪制方形
3. 工廠模式中的多態
abstract class Product {abstract void use();
}class ConcreteProduct extends Product {void use() { System.out.println("使用具體產品"); }
}class Factory {Product create() { return new ConcreteProduct(); } // 返回抽象類型
}
1.7.5 多態的使用技巧
1. instanceof與類型轉換
if (animal instanceof Dog) {Dog dog = (Dog) animal; // 向下轉型dog.bark();
}
2 避免過度使用instanceof,在instanceof方法講解中提到過,這里不再贅述
3.?模板方法模式:
abstract class Game {// 模板方法(final防止重寫)final void play() {initialize();startPlay();endPlay();}abstract void initialize();abstract void startPlay();abstract void endPlay();
}
1.8 小結
最近在看面試題,剛好放幾個出來看看你答不答的出來。
Q1: 為什么字段沒有多態性?
class Parent { String name = "Parent"; }
class Child extends Parent { String name = "Child"; }Parent obj = new Child();
System.out.println(obj.name); // 輸出"Parent"(字段靜態綁定)
字段訪問在編譯時確定,不參與運行時多態
Q2: 靜態方法能否實現多態?
不能,靜態方法調用是靜態綁定的:
class Parent {static void method() { System.out.println("Parent"); }
}class Child extends Parent {static void method() { System.out.println("Child"); }
}Parent obj = new Child();
obj.method(); // 輸出"Parent"(只看引用類型)
Q3: 構造方法中的多態問題:為什么在構造方法中調用可重寫方法是危險的做法?
根本原因在于Java的初始化順序問題。
(1)?對象構造的生命周期
Java對象構造遵循嚴格的初始化順序:
-
分配對象內存空間(所有字段設為默認值:0/null/false)
-
調用父類構造方法(遞歸向上)
-
初始化當前類的實例變量(按聲明順序)
-
執行當前類構造方法體
(2)?問題發生的時機
當父類構造方法調用可重寫方法時:
-
子類字段尚未初始化(仍為默認值)
-
但方法已經被子類重寫,會調用子類版本
-
導致子類方法操作未初始化的字段
舉個例子?
class Parent {Parent() {printLength(); // 危險調用}void printLength() {System.out.println("Parent method");}
}class Child extends Parent {private String text = "hello"; // 還未初始化@Overridevoid printLength() {System.out.println(text.length()); // text此時為null!}
}new Child(); // 拋出NullPointerException
?Q4:?instanceof
?和?getClass()
?有什么區別?
-
instanceof
?檢查類型繼承關系(包含子類) -
getClass() == SomeClass.class
?檢查精確類型匹配
Number num = new Integer(10);System.out.println(num instanceof Number);? // true
System.out.println(num.getClass() == Number.class);? // false
Q5: 為什么需要?instanceof
?而不能直接強制轉換?
直接強制轉換可能拋出?ClassCastException
:
Object obj = "Hello";
Integer num = (Integer) obj;? // 運行時拋出ClassCastException
Q6: 如何檢查泛型的具體類型?
由于類型擦除,需要通過其他方式(如傳遞Class對象):
<T> void checkType(Object obj, Class<T> type) {if (type.isInstance(obj)) {T t = type.cast(obj);// 處理t}
}
Q7: JVM如何實現方法重寫?
通過虛方法表(vtable)實現:
-
每個類有一個方法表,包含方法實際入口地址
-
子類方法表包含繼承的方法和重寫的方法
-
調用時根據對象實際類型查找方法表
Q8: 為什么靜態方法不能重寫?
因為方法綁定時機不同:
-
靜態方法:編譯時靜態綁定(根據引用類型)
-
實例方法:運行時動態綁定(根據實際對象類型)
Q9: 構造方法能被重寫嗎?
不能,因為:
-
構造方法名必須與類名相同
-
子類構造方法必須調用父類構造(通過super)
-
本質上是不同的方法
Q10: 如何強制子類重寫方法?
兩種方式:
-
聲明為抽象方法
abstract class Parent {abstract void mustOverride(); }
-
拋出UnsupportedOperationException
class Parent {void mustOverride() {throw new UnsupportedOperationException("必須重寫此方法");} }
不知道你答出來了沒。畢竟這幾個題看似很難,實際上一點也不簡單。建議多看、多練、多理解。
?