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

Java 繼承中的向上轉型與記憶體配置深度解析

     編輯:Java 綜合教學

Java 繼承中的向上轉型與記憶體配置深度解析

在研讀《瘋狂 Java 講義》時,一段關於繼承與轉型的程式碼讓我陷入深思。程式碼定義了一個抽象父類別 People 和子類別 Student,兩者都擁有同名的實例變數 tag 和 name。透過向上轉型存取父類別的欄位,以及呼叫繼承來的方法,輸出結果卻出乎意料:

package inheritance;
abstract class People {
    public String tag = "瘋狂Java講義";
    public String name = "Parent";
    String getName() { return name; }
}
class Student extends People {
    String tag = "輕量級Java EE企業應用實戰";
    public String name = "Student";
}
public class HideTest2 {
    public static void main(String[] args) {
        Student d = new Student();
        System.out.println(((People)d).tag); // 輸出:瘋狂Java講義
        System.out.println(d.getName());    // 輸出:Parent
    }
}

執行結果:

瘋狂Java講義
Parent

核心疑問:子類別實例化時,父類別對象是否真實存在?

許多初學者(包括當時的我)會直覺認為:new Student() 必須先建立 People 對象,但 People 是抽象類別,無法實例化,這似乎產生了矛盾。實際上,子類別實例化不會產生獨立的父類別對象。JVM 僅在子類別對象的記憶體空間內,配置了父類別定義的實例變數(即「繼承下來的欄位」),並呼叫父類別的建構子進行初始化。建構子的角色是「初始化」,而非「產生對象」。new 關鍵字才是產生對象的唯一指令。

記憶體模型圖解

一個 Student 對象在堆記憶體中包含兩組同名變數:

  • 來自 People 的 tag、name
  • 子類別自行定義的 tag、name(隱藏父類別同名欄位)

記憶體配置示意圖

靜態綁定 vs 動態綁定:為何輸出「Parent」而非「Student」?

這涉及 Java 的兩種綁定機制:

  • 靜態綁定(編譯期):適用於成員變數。編譯器根據參考變數的宣告型別(此處為 People)決定存取哪個欄位。因此 ((People)d).tag 和 d.getName() 內部存取 name 時,皆綁定至父類別的欄位。
  • 動態綁定(執行期):適用於實例方法。JVM 根據對象的實際型別(此處為 Student)決定呼叫哪個方法。若子類別覆寫了方法,則執行子類別版本。

在範例中,getName() 未被覆寫,故執行父類別版本,回傳父類別的 name("Parent")。若在 Student 中覆寫 getName() 回傳 this.name,則會輸出 "Student",因為 this 指向子類別對象,存取的是子類別的 name 欄位。

專家觀點總結

「不會產生父類別對象,只是用了父類別的建構函數而已……變數是靜態綁定,方法是動態綁定。子類別對象內存中有兩份變數,一份繼承自父類別,一份屬於自己。絕對不會產生父類別對象,否則抽象類別豈能實例化?」

深入理解 <init> 初始化方法與 new 指令的區別,能徹底釐清此觀念。子類別建構子首行隱含的 super() 呼叫,只是觸發父類別的初始化邏輯,並非建立父類別對象。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved