首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 編譯器如何處理方法重載與覆蓋:靜態綁定 vs 動態分發

     編輯:Java 綜合教學

Java 編譯器如何處理方法重載與覆蓋:靜態綁定 vs 動態分發

多態是面向對象核心特性,分為編譯期多態(重載)和運行期多態(覆蓋)。本文通過字節碼視角解析 javac 如何為這兩種多態生成調用指令,揭示 JVM 方法調用的底層機制。

代碼示例

class A {
    public void m(int i) { System.out.println("int"); }
    public void m(String s) { System.out.println("string"); } // 重載
}

class B extends A {
    @Override
    public void m(int i) { System.out.println("overridden int"); } // 覆蓋
}

public class PolymorphismDemo {
    public static void main(String[] args) {
        A a = new A();
        a.m(1);        // 調用 A.m(int)
        a.m("abc");    // 調用 A.m(String)

        A b = new B(); // 多態:引用類型 A,實際類型 B
        b.m(1234);     // 調用 B.m(int) —— 動態分發
    }
}

編譯期處理:重載解析

  • 靜態綁定:編譯器根據引用類型和參數靜態類型,在編譯期確定調用哪個重載方法。
  • 生成 invokevirtual 指令,常量池存儲方法符號引用(類名、方法名、描述符)。
  • a.m(1) → 解析為 A.m(int);a.m("abc") → 解析為 A.m(String)。
  • 若無匹配方法,編譯報錯(如傳 long 調用 m(int) 需顯式轉型)。

運行期處理:覆蓋分發

  • 動態分發:JVM 根據對象實際類型在運行期確定調用哪個覆蓋方法。
  • b.m(1234):引用類型 A,實際類型 B。JVM 在 B 的方法表中查找 m(int),找到 B.m(int) 執行。
  • 核心數據結構:方法表,每個類構建一張表,存儲所有非私有實例方法的入口地址,子類繼承並覆蓋父類表項。

JVM 四條方法調用指令對比

指令 用途 綁定類型 典型場景
invokestatic 靜態方法 靜態 Math.max()
invokespecial 私有方法、構造器、父類方法 靜態 super.toString()
invokevirtual 普通實例方法 動態 多態調用 obj.method()
invokeinterface 接口方法 動態 list.size()

對象內存佈局與方法表

對象內存佈局

  • 對象頭:Mark Word(哈希碼、GC 信息、鎖狀態)、Klass Pointer(指向類元數據)。
  • 實例數據:字段值。
  • 對齊填充:8 字節對齊。
  • Klass Pointer 指向方法區中的 Klass 元數據,包含方法表、常量池指針等。

關鍵結論

  1. 重載:編譯器根據參數靜態類型決議,屬於靜態多態,零運行期開銷。
  2. 覆蓋:JVM 通過方法表按對象實際類型動態分發,屬於動態多態,有間接查找開銷(現代 JIT 內聯優化可消除)。
  3. 私有/靜態/構造器/父類方法:編譯期確定,使用 invokespecial/invokestatic,不參與動態分發。
  4. invokedynamic(JDK 7+):支持動態語言,Lambda 表達式底層實現。

延伸閱讀

  • 《深入理解 Java 虛擬機》第 8 章:虛擬機字節碼執行引擎
  • JVM 規範:方法調用指令集
  • HotSpot 源碼:src/hotspot/share/oops/method.hpp 方法表實現
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved