Java 繼承機制中,「子類繼承父類所有非 private 成員」是常識,但**字段 (Field/成員變量) 不支持多態**,這導致經典的字段隱藏問題。本文通過代碼實驗、字節碼視角、JLS 規範解讀,徹底釐清父子類同名字段的訪問規則,避免生產 Bug。
public class FieldHidingDemo {
int value = 100; // 父類字段
public static void main(String[] args) {
Child child = new Child();
System.out.println("child.value = " + child.value); // 200
System.out.println("((Parent)child).value = " + ((Parent)child).value); // 100
}
}
class Child extends FieldHidingDemo {
int value = 200; // 子類字段:隱藏而非覆寫
}
輸出:
child.value = 200 ((Parent)child).value = 100
| 特性 | 實例方法 | 實例字段 |
|---|---|---|
| 機制 | 動態分發 (虛方法表) | 靜態綁定 (編譯期類型決定) |
| 關鍵字 | @Override (可選但推薦) | 無 (隱式隱藏) |
| 多態性 | 支持 (運行時類型決定) | **不支持** (編譯時類型決定) |
| 父類訪問 | super.method() | super.field / ((Parent)obj).field |
| static 方法 | 隱藏 (靜態綁定) | 同樣隱藏 |
編譯後 Child.class 含兩個 value 字段:
Constant pool: #2 = Fieldref Parent.value:I #3 = Fieldref Child.value:I // 訪問 child.value (Child 類型引用) 0: getfield #3 // Field Child.value:I // 訪問 ((Parent)child).value (Parent 類型引用) 3: getfield #2 // Field Parent.value:I
關鍵證據:getfield 指令根據**引用的靜態類型** (編譯期已知) 解析常量池索引,直接訪問對應類別的字段偏移量,**無任何運行時查找**。
super.field 或強轉。Primary.field 的類型為 Primary 的編譯時類型聲明的字段類型。private + public getter/setter。子類若需擴展行為,覆寫方法而非重新聲明字段。javac -Xlint:hidden 或 IDE 設置 (IntelliJ: Inspection > Java > Inheritance > Field hides superclass field)。_field,子類擴展 _fieldEx,或統一前綴 mField 避免同名。Child 對象通過 Parent 引用訪問字段的場景。
class Parent {
private int value = 100;
public int getValue() { return value; }
public void setValue(int v) { value = v; }
}
class Child extends Parent {
// 無字段聲明,繼承父類私有字段 (不可直接訪問)
// 若需擴展邏輯,覆寫方法:
@Override
public int getValue() {
return super.getValue() + 100; // 200
}
}
// 使用
Parent obj = new Child();
System.out.println(obj.getValue()); // 200 - 多態生效!
Java 字段隱藏是語言設計的歷史包袱 (為了 C++ 兼容性保留靜態綁定語義)。**鐵律:字段不參與多態,只有方法才有多態**。堅持封裝、私有化字段、提供訪問器、覆寫方法,從根源消除此類陷阱,寫出可維護、可擴展的面向對象代碼。