為什麼 Java 中不允許使用 super.super.method()
1. 概述
在 Java 中,繼承是跨類別重複使用和擴充功能的強大機制。透過利用super
關鍵字,我們可以從子類別存取父類別的方法和屬性。
然而,Java 中的一個限制讓我們感到驚訝:不允許super.super.method()
。
在本教程中,我們將深入探討為什麼會出現這種情況以及 Java 的設計理念如何塑造這種行為。
2.問題介紹
像往常一樣,讓我們透過範例來理解問題。假設我們有三個類別:
class Person {
String sayHello() {
return "Person: How are you?";
}
}
class Child extends Person {
@Override
String sayHello() {
return "Child: How are you?";
}
String mySuperSayHello() {
return super.sayHello();
}
}
class Grandchild extends Child {
@Override
String sayHello() {
return "Grandchild: How are you?";
}
String childSayHello() {
return super.sayHello();
}
String personSayHello() {
return super.super.sayHello();
}
}
如上面的程式碼所示,這三個類別具有以下層次結構:
[Person] <--subtype-- [Child] <--subtype-- [Grandchild]
此外,所有子類型都會重寫父類別的sayHello()
方法。
值得注意的是, Grandchild
有兩個附加方法: childSayHello()
和personSayHello().
它們的實作很簡單,在childSayHello()
中呼叫super.sayHello()
,在 personSayHello( super.super.sayHello()
personSayHello().
然而, Java 不編譯**super.super.sayHello()**
並抱怨:
java: <identifier> expected
那麼接下來,我們就來了解為什麼Java中不允許呼叫super.super.method()
,並弄清楚如何實作呼叫祖父類別的方法。
3. 為什麼Java中不允許呼叫super.super.method()
它是故意設計來禁止 Java 中的super.super.method()
。接下來我們就來分析一下原因。
3.1.違反封裝性
Java 的設計以封裝和抽象化為核心原則。這些原則規定,一個類別應該只關心其直接父級,而不是其祖級或更高層級的實作細節。
直接呼叫祖父母的方法會忽略父級在控制繼承鏈中的角色,從而暴露父級可能故意隱藏或更改的內部邏輯。
接下來,讓我們來看一個新的例子,它說明了為什麼 Java 禁止使用super.super.method()
以及如果允許使用 super.super.method() 可能會導致什麼問題。
3.2. Players
例子
首先,我們聲明一個Player
類別:
class Player {
private String name;
private String type;
private int rank;
public Player(String name, String type, int rank) {
this.name = name;
this.type = type;
this.rank = rank;
}
// ...getter and setter methods are omitted
}
name
和rank
屬性很簡單。 type
屬性保存玩家屬於哪個區域,例如「 football
」或「 tennis
」。
接下來,讓我們建立一組Player
集合類型:
class Players {
private List<Player> players = new ArrayList();
protected boolean add(Player player) {
return players.add(player);
}
}
class FootballPlayers extends Players {
@Override
protected boolean add(Player player) {
if (player.getType().equalsIgnoreCase("football")) {
return super.add(player);
}
throw new IllegalArgumentException("Not a football player");
}
}
class TopFootballPlayers extends FootballPlayers {
@Override
protected boolean add(Player player) {
if (player.getRank() < 10) {
return super.add(player);
}
throw new IllegalArgumentException("Not a top player");
}
}
如程式碼所示,三個Player
集合類別的繼承層次如下:
[Players] <--subtype-- [FootballPlayers] <--subtype--[TopFootballPlayers]
FootballPlayers
和TopFootballPlayers
都重寫其超類別中的add()
方法。當然,他們在add()
方法中添加了不同的檢查,以確保只能添加具有所需類型的Player
物件。例如, TopFootballPlayers.add()
要求球員的排名高於**TopFootballPlayers.add()
super.add()
**
測試可以快速顯示此繼承層次結構如何運作:
Player liam = new Player("Liam", "football", 9);
Player eric = new Player("Eric", "football", 99);
Player kai = new Player("Kai", "tennis", 7);
TopFootballPlayers topFootballPlayers = new TopFootballPlayers();
assertTrue(topFootballPlayers.add(liam));
Exception exEric = assertThrows(IllegalArgumentException.class, () -> topFootballPlayers.add(eric));
assertEquals("Not a top player", exEric.getMessage());
Exception exKai = assertThrows(IllegalArgumentException.class, () -> topFootballPlayers.add(kai));
assertEquals("Not a football player", exKai.getMessage());
我們可以看到, eric
是一名足球員,但他的rank
低於 10 TopFootballPlayers.add()
類似地, kai
也被TopFootballPlayers.add()
方法拒絕,因為他是網球運動員,儘管他的rank
= **TopFootballPlayers**
FootballPlayer's add()
3.3.如果允許super.super.add()
現在,假設允許super.super.method()
調用,我們可以這樣重寫TopFootballPlayers.add()
:
class TopFootballPlayers extends FootballPlayers {
@Override
protected boolean add(Player player) {
if (player.getRank() < 10) {
return super.super.add(player); // call grandparent's add() method directly
}
throw new IllegalArgumentException("Not a top player");
}
}
然後,我們可以新增任何Player
實例 rank < 10.
**kai
TopFootballPlayers
add() kai
**因此,它打破了其父級中的不變量: FooballPlayers
。
3.4.其他原因
Java 不允許super.super.method().
Java 在其語言設計中優先考慮簡單性和清晰性。允許super.super.method()
會引入不必要的複雜性,要求語言動態解析繼承層次結構的更深層。這種複雜性會使程式碼更難閱讀和推理,尤其是在較大的程式碼庫中。透過限制僅存取直接父級,Java 確保繼承鏈保持清晰且可管理。
除了設計簡單之外,另一個原因是「 super
」**不一定有「 super”.
**因此, super.super
不存在。
我們知道Java中所有的類別都是Object
的子類別。因此,我們創建的任何類別都有一個有效的「 super
」。但是,如果OurClass
直接繼承於Object
,則ourClass.super
是Object,
且ourClass.super.super
不存在。這使得super.super
無效。
然而,在某些情況下,我們想要呼叫祖父類別的方法。接下來我們來看看如何實現。
4. 解決方法:間接調用
Java 反射非常強大,幾乎可以做任何事。使用反射也可以讓我們實作super.super.method()
。但在本教程中我們不會深入探討此類反射實現,因為它仍然違反封裝性。
儘管不允許super.super.method()
,但 Java 提供了其他機制來實現所需的功能。例如,我們可以重構父類別:如果子類別需要存取其祖父的方法,父類別可以明確地公開它。
接下來,讓我們展示如何使用我們的Person-Child-Grandchild:
class Child extends Person {
// ...same code omitted
String mySuperSayHello() {
return super.sayHello();
}
}
class Grandchild extends Child {
// ...same code omitted
String personSayHello() {
return super.mySuperSayHello();
}
}
我們的目標是從 Grandchild 呼叫Person.sayHello()
Grandchild.
然後,如程式碼所示,我們可以在Child
類別 Child.mySuperSayHello( Person.sayHello()
Child.mySuperSayHello():
Grandchild aGrandchild = new Grandchild();
assertEquals("Grandchild: How are you?", aGrandchild.sayHello());
assertEquals("Child: How are you?", aGrandchild.childSayHello());
assertEquals("Person: How are you?", aGrandchild.personSayHello());
這種方法可以在不違反封裝的情況下完成工作。
5. 結論
在本文中,我們討論了為什麼 Java 不允許super.super.method().
缺少super.super.method()
並不是一種疏忽,而是符合封裝、抽象和簡單原則的故意設計選擇。
Java 透過限制子類別僅與其直接父類別互動來鼓勵更好的程式碼組織和可維護性。
與往常一樣,範例的完整原始程式碼可在 GitHub 上取得。