編程語言Java入門——核心技術篇(一)封裝、繼承和多態

同專欄基礎知識篇寫在這里,有興趣的可以去看看:

編程語言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)

  • 無任何方法:

    • 例如?SerializableCloneable,僅用于標記類具有某種能力。

  • 通過反射或類型檢查使用:

    • 例如?if (obj instanceof Serializable) { ... }

1.1.3 接口的應用場景

1. 定義通用行為(如?ComparableRunnable)。

2. 實現回調機制(如事件監聽器?EventListener)。

3. 依賴注入(Spring 框架中廣泛使用接口解耦)。

4. 策略模式(通過不同實現類切換算法)。

5. API 設計(如 JDBC 的?ConnectionStatement?接口)。

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
protectedprotected, 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() 方法

契約:

  1. 自反性:x.equals(x)?必須返回 true

  2. 對稱性:x.equals(y)?與?y.equals(x)?結果相同

  3. 傳遞性:如果?x.equals(y)?且?y.equals(z),則?x.equals(z)

  4. 一致性:多次調用結果不變(除非對象被修改)

  5. 非空性:x.equals(null)?必須返回 false

重寫步驟:

  1. 檢查是否同一引用

  2. 檢查是否為 null

  3. 檢查是否同類(考慮繼承時使用 getClass() 或 instanceof)

  4. 強制類型轉換

  5. 比較關鍵字段

示例:

@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 對象契約明確規定:

  1. 如果?obj1.equals(obj2)?為?true,則?obj1.hashCode()?必須等于?obj2.hashCode()

  2. 但?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 接口

步驟:

  1. 實現?Cloneable?標記接口

  2. 重寫?clone()?方法(提升為public)

  3. 對每個引用字段遞歸調用?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: 構造方法能被重寫嗎?

不能,因為:

  1. 構造方法名必須與類名相同

  2. 子類構造方法必須調用父類構造(通過super)

  3. 本質上是不同的方法

Q10: 如何強制子類重寫方法?

兩種方式:

  • 聲明為抽象方法

    abstract class Parent {abstract void mustOverride();
    }
  • 拋出UnsupportedOperationException

    class Parent {void mustOverride() {throw new UnsupportedOperationException("必須重寫此方法");}
    }

不知道你答出來了沒。畢竟這幾個題看似很難,實際上一點也不簡單。建議多看、多練、多理解。

    ?

    本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
    如若轉載,請注明出處:http://www.pswp.cn/diannao/92029.shtml
    繁體地址,請注明出處:http://hk.pswp.cn/diannao/92029.shtml
    英文地址,請注明出處:http://en.pswp.cn/diannao/92029.shtml

    如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

    相關文章

    【39】MFC入門到精通——C++ /MFC操作文件行(讀取,刪除,修改指定行)

    文章目錄1 通過關鍵詞&#xff0c;讀取某一行 &#xff08;3種方法&#xff09;2 刪除 指定行3 修改 指定行1 通過關鍵詞&#xff0c;讀取某一行 &#xff08;3種方法&#xff09; 通過定位關鍵詞&#xff0c;讀取某一行信息,返回CString //通過定位關鍵詞&#xff0c;讀取某…

    5 種可行的方法:如何將 Redmi 聯系人備份到 Mac

    將 Redmi 聯系人備份到 Mac 是防止因手機損壞、丟失或更換設備而導致數據丟失的重要措施。雖然云服務提供了便利性&#xff0c;但擁有離線備份可以提供額外的安全性&#xff0c;而無需完全依賴互聯網。如果您想知道如何將 Redmi 聯系人備份到 Mac&#xff0c;本文將為您介紹 5 …

    LeRobot 具身智能機械臂 SO-ARM100 從搭建到訓練全流程

    今天給大家分享一下 LeRobot 具身智能機械臂 SO-ARM100 的完整使用流程&#xff0c;包括設備組裝、環境配置、遠程控制、數據錄制到模型訓練的全過程。適合剛入門具身智能的小伙伴參考學習。 一、前期準備與資源獲取 在開始之前&#xff0c;我們需要準備好相關的資源和工具&a…

    LINUX720 SWAP擴容;新增邏輯卷;邏輯卷擴容;數據庫遷移;gdisk

    SWAP空間擴展 方法一 增加硬盤或分區擴展 swap -s mkswap /dev/sdd6 blkid /dev/sdd6 swapon /dev/sdd6 swapon -s vim /etc/fstab /dev/sdd6 swap swap defaults 0 0 開機自動擴容 swap -s [rootweb ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sd…

    Python 進程間通信:TCP安全加密數據傳輸

    最近在寫安全方面的程序&#xff0c;有需求&#xff0c;就做了這些TCP加密數據傳輸類。 utils.safeUtils的內容詳見&#xff1a; SafeObj&#xff1a;Python 高安全性加密數據容器類-CSDN博客SafeKey&#xff1a;Python 高安全性加密密碼容器類-CSDN博客 如有任何問題或漏洞歡迎…

    Windows批量修改文件屬性方法

    標題使用icacls命令&#xff08;推薦批量操作&#xff09;打開管理員權限的命令提示符&#xff08;CMD&#xff09;執行以下命令&#xff1a;cmd icacls "文件夾路徑" /grant 用戶名:(OI)(CI)F /T /C 參數說明&#xff1a;(OI)&#xff1a;對象繼承 - 適用于文件夾(C…

    Entity Component System架構

    ECS架構 1 簡介 在當今快速發展的軟件開發領域&#xff0c;游戲開發、實時模擬等場景對系統的性能、靈活性和可擴展性提出了極高的要求。傳統的面向對象架構在面對復雜且動態變化的實體時&#xff0c;往往會出現代碼耦合度高、擴展性差等問題。? ECS&#xff08;Entity - Com…

    .vscode 擴展配置

    一、vue快捷鍵配置 在項目.vscode下新建vue3.0.code-snippets 每當輸入vue3.0后自動生成代碼片段 {"Vue3.0快速生成模板": {"scope": "vue","prefix": "Vue3.0","body": ["<template>"," &…

    一個基于阿里云的C端Java服務的整體項目架構

    1.背景介紹 總結一下工作使用到的基于通常的公有云的項目整體架構&#xff0c;如何基于公有云建設安全可靠的服務&#xff0c;以阿里云為例的整體架構&#xff1b;1. 全局流量治理層&#xff08;用戶請求入口&#xff09;1.1 域名與 DNS 解析域名注冊與備案&#xff1a;通過阿里…

    《剝開洋蔥看中間件:Node.js請求處理效率與錯誤控制的深層邏輯》

    在Node.js的運行時環境中&#xff0c;中間件如同一系列精密咬合的齒輪&#xff0c;驅動著請求從進入到響應的完整旅程&#xff0c;而洋蔥模型則是這組齒輪的傳動系統。它以一種看似矛盾的方式融合了順序與逆序、分離與協作——讓每個處理環節既能獨立工作&#xff0c;又能感知全…

    GaussDB union 的用法

    1 union 的作用union 運算符用于組合兩個或更多 select 語句的結果集。2 union 使用前提union 中的每個 select 語句必須具有相同的列數這些列也必須具有相似的數據類型每個 select 語句中的列也必須以相同的順序排列3 union 語法select column_name(s) from table1 union sele…

    構建足球實時比分APP:REST API與WebSocket接入方案詳解

    在開發足球實時比分應用時&#xff0c;數據接入方式的選擇直接影響用戶體驗和系統性能。本文將客觀分析REST API和WebSocket兩種主流接入方案的技術特點、適用場景和實現策略&#xff0c;幫助開發者做出合理選擇。一、REST API&#xff1a;靈活的數據獲取方案核心優勢標準化接口…

    Linux文件系統三要素:塊劃分、分區管理與inode結構解析

    理解文件系統 我們知道文件可以分為磁盤文件和內存文件&#xff0c;內存文件前面我們已經談過了&#xff0c;下面我們來談談磁盤文件。 目錄 一、引入"塊"概念 解析 stat demo.c 命令輸出 基本信息 設備信息 索引節點信息 權限信息 時間戳 二、引入"分區…

    基于paddleDetect的半監督目標檢測實戰

    基于paddleDetect的半監督目標檢測實戰前言相關介紹前提條件實驗環境安裝環境項目地址使用paddleDetect的半監督方法訓練自己的數據集準備數據分割數據集配置參數文件PaddleDetection-2.7.0/configs/semi_det/denseteacher/denseteacher_ppyoloe_plus_crn_l_coco_semi010.ymlPa…

    計算機網絡:(十)虛擬專用網 VPN 和網絡地址轉換 NAT

    計算機網絡&#xff1a;&#xff08;十&#xff09;虛擬專用網 VPN 和網絡地址轉換 NAT前言一、虛擬專用網 VPN1. 基礎概念與作用2. 工作原理3. 常見類型4. 協議對比二、NAT&#xff1a;網絡地址轉換1. 基礎概念與作用2. 工作原理與類型3. 優缺點與問題4. 進階類型三、VPN 與 N…

    數位 dp

    數位dp 特點 問題大多是指“在 [l,r][l,r][l,r] 的區間內&#xff0c;滿足……的數字的個數、種類&#xff0c;等等。” 但是顯然&#xff0c;出題人想要卡你&#xff0c;rrr 肯定是非常大的&#xff0c;暴力枚舉一定超時。 于是就有了數位 dp。 基本思路 數位 dp 說白了…

    Selector的用法

    Selector的用法 Selector是基于lxml構建的支持XPath選擇器、CSS選擇器&#xff0c;以及正則表達式&#xff0c;功能全面&#xff0c;解析速度和準確度非常高 from scrapy import Selectorbody <html><head><title>HelloWorld</title></head>&…

    Netty封裝Websocket并實現動態路由

    引言 關于Netty和Websocket的介紹我就不多講了,網上一搜一大片。現如今AI的趨勢發展很熱門,長連接對話也是會經常接觸到的,使用Websocket實現長連接,那么很多人為了快速開發快速集成就會使用spring-boot-starter-websocket依賴快速實現,但是注意該實現是基于tomcat的,有…

    行為型設計模式:解釋器模式

    解釋器模式 解釋器模式介紹 解釋器模式使用頻率不算高&#xff0c;通常用來描述如何構建一個簡單“語言”的語法解釋器。它只在一些非常特定的領域被用到&#xff0c;比如編譯器、規則引擎、正則表達式、SQL 解析等。不過&#xff0c;了解它的實現原理同樣很重要&#xff0c;能…

    SaTokenException: 未能獲取對應StpLogic 問題解決

    &#x1f4dd; Sa-Token 異常處&#xff1a;未能獲取對應StpLogic&#xff0c;typeuser&#x1f9e8; 異常信息 cn.dev33.satoken.exception.SaTokenException: 未能獲取對應StpLogic&#xff0c;typeuser拋出位置&#xff1a; throw new SaTokenException("未能獲取對應S…