Java 多態、向上/向下轉型、protected 與 private 訪問控制,單獨看都不難,但一旦結合在一起,往往令開發者困惑不已。本文通過一個經典案例,層層剖析七大「困惑點」,帶你徹底搞懂多態運行機制與編譯期綁定規則。
兩個類:父類 A、子類 B extends A。關鍵點:
protected int protectedab 字段A 定義 private void privateTest()、protected void protectedTest(Object)、protected void protectedTest(String)B 重寫 protectedTest(Object)main 方法在 A 中,包含 7 個測試點A a2 = b1; 發生了什麼?經典向上轉型。a2 是類型 A 的引用,指向 B 類型對象。Java 規則:父類引用可指向子類實例。
b1 = a1; 編譯報錯?b1 是 B 引用,a1 是 A 實例(非 B 或其子類)。向下轉型需顯式強轉:b1 = (B) a1;,且運行時需確保對象真為 B 類型。
b1.privateTest(); 編譯失敗?private 方法僅限定義類內部訪問。子類 B 不可見,即使對象實際是 B 實例。私有方法不參與多態。
b1.protectedTest(helloObj); 輸出 B 的方法?helloObj 編譯類型為 Object。JVM 採用動態綁定:運行時根據對象真實類型(B)分派方法,而非引用類型。故調用 B.protectedTest(Object)。
b1.protectedTest(helloStr); 編譯報 ambiguous 錯誤?helloStr 編譯類型 String。兩條路徑均可行:
String → Object,調用 B.protectedTest(Object)A.protectedTest(String)(繼承而來)編譯器無法判定優先級,拋出 reference ambiguous。隱式歧義更危險(如參數順序互換導致運行時行為不符預期)。
a2.protectedTest(helloObj); 輸出 B 的方法?a2 編譯類型 A,實際指向 B 對象。動態綁定生效,運行時分派至 B 實現。
a2.protectedTest(helloStr); 為什麼輸出 A 的方法且不報錯?關鍵在於方法表差異:
a2=b1 賦值後,存在兩個句柄,指向同一數據內存,但方法表不同:a2 用 A 的方法表,b1 用 B 的a2 方法表含 protectedTest(String),精確匹配,無歧義 → 編譯通過,靜態綁定至 A 版本b1 方法表無 String 版本,只有 Object 版本,引發歧義 → 編譯報錯JVM 規範:對象引用指向句柄,句柄含方法表指針與類對象指針。向上轉型不改變對象,但改變方法表視圖。