super 與 this 是 Java 繼承體系中最核心卻最易誤解的兩個關鍵字。本文通過 6 個層層遞進的代碼範例,系統剖析它們在繼承鏈中的真實行為,揭開「為什麼有時輸出相同」的謎團。
外部的
super/this僅決定「調用父類還是子類的方法」,不影響方法體內部的super/this綁定。
方法體內:super.xxx永遠綁定「所在類的父類」;this.xxx分三種情況綁定。
class Grandfather { String name = "grandfather"; }
class Father extends Grandfather {
String name = "father";
void show() {
System.out.println("father.show-->" + this.name + "--" + super.name);
}
}
class Son extends Father {
void rs() {
super.show(); // 輸出: father.show-->father--grandfather
this.show(); // 輸出: father.show-->father--grandfather (相同!)
}
}
現象:super.show() 與 this.show() 輸出相同。
原因:Son 未覆寫 show(),繼承自 Father。this.show() 解析為 Son 的 show() (實為繼承的 Father.show());super.show() 解析為 Son 父類即 Father 的 show()。兩者指向同一方法。
getClass() 的特殊性public class Test {
public void test() {
System.out.println(super.getClass()); // class Test
System.out.println(this.getClass()); // class Test
}
}
getClass() 為 final 方法,編譯期靜態綁定,this.getClass() 直接解析為當前類 (Test) 的方法;super.getClass() 解析為父類 (Object) 的方法,但 Object.getClass() 邏輯是返回運行時對象類型 (Test.class),故結果相同。
class Son extends Father {
void show() { // 覆寫
System.out.println("son.show-->" + this.name + "--" + super.name);
}
void rs() {
super.show(); // father.show-->father--grandfather
this.show(); // son.show-->father--father (不同!)
}
}
此時 this.show() 調用 Son 覆寫版本,this.name 為 Father.name (因 Son 無 name,向上查找),super.name 為 Father 父類即 Grandfather.name?實測 super.name 指 Father 的父類欄位?
修正:Son.show() 中 super.name 訪問 Son 父類 Father 的 name 欄位 → father。故輸出 son.show-->father--father。
class Father {
final void m6() { ... } // final
static void m7() { ... } // static
void m8() { ... } // 普通
}
class Child extends Father {
void m8() { ... } // 覆寫 m8
}
Child c = new Child();
c.m6(); // father's m6 (final, 靜態綁定)
c.m7(); // father's m7 (static, 靜態綁定)
c.m8(); // child's m8 (動態綁定)
規律:final、static、private、構造方法調用 → 編譯期靜態綁定;普通實例方法 → 運行期動態綁定。
// 隱藏:子類聲明同名欄位
class Son extends Father { String name = "son"; } // 隱藏 Father.name
// 修改:構造器賦值,不聲明新欄位
class Son extends Father {
public Son() { name = "son"; } // 修改繼承的 Father.name
}
關鍵差異:
super.name 仍訪問父類值super.name 讀到新值若 Father 也隱藏了 Grandfather.name,則修改只影響 Father.name;若 Father 未隱藏,修改會透傳到 Grandfather.name。
class Father extends Grandfather {
void show() {
System.out.println(this.name); // Father.name (變量/靜態方法: 編譯期綁定所在類)
System.out.println(this.toString()); // Son.toString() (普通方法: 運行期綁定運行對象)
System.out.println(this.toString2()); // Father.toString2() (靜態方法: 編譯期綁定)
System.out.println(super.name); // Grandfather.name
System.out.println(super.toString()); // Grandfather.toString()
System.out.println(super.toString2());// Grandfather.toString2()
}
}
class Son extends Father {
void rs() {
super.show(); // 進入 Father.show()
this.show(); // 進入 Father.show() (Son 未覆寫)
}
}
輸出驗證:兩次調用 Father.show() 內部 this/super 綁定完全一致,外部呼叫方式 (super.show() vs this.show()) 不改變方法內部解析邏輯。
| 場景 | super.xxx | this.xxx |
|---|---|---|
| 變量 / 靜態方法 / final / private / 構造器 | 所在類父類的成員 (編譯期確定) | 所在類的成員 (編譯期確定) |
| 普通實例方法 | 所在類父類的方法 (編譯期確定調用點) | 運行時對象的方法 (動態綁定) → 若子類未覆寫/隱藏,則使用繼承自父類的版本 |
super.getClass() 為何返回子類 Class?class Father {
void show() {
System.out.println(this.getClass()); // class Son
System.out.println(super.getClass()); // class Son (!)
System.out.println(this.getClass().getSuperclass()); // class Father
System.out.println(super.getClass().getSuperclass()); // class Father
}
}
class Son extends Father { void rs() { super.show(); this.show(); } }
解析:getClass() 為 final + Native 實現,邏輯為 GetObjectClass(this)。this 指運行時對象 (Son),故無論 this.getClass() 還是 super.getClass(),this 都是 Son 實例,返回 Son.class。getSuperclass() 再取父類 Father.class。
super 永遠指向「所在類的父類」,編譯期靜態確定this 分兩類:變量/靜態/final/private/構造器 → 編譯期綁定所在類;普通方法 → 運行期綁定實際對象super.show() vs this.show()) 只決定「進入哪個類的方法」,不影響方法內部的 super/this 解析getClass() 永遠返回運行時對象類型,與 super/this 無關掌握這些規則,繼承鏈中的方法調用與欄位訪問將不再有歧義。