詳解 Java 17 中新推出的密封類

程式猿DD發表於2022-05-06

Java 17推出的新特性Sealed Classes經歷了2個Preview版本(JDK 15中的JEP 360、JDK 16中的JEP 397),最終定稿於JDK 17中的JEP 409。Sealed Classes有兩種主流翻譯:密封類、封閉類。個人喜歡前者多一些,所以在本文中都稱為密封類。其實Sealed Classes的其他許多語言中並不是什麼新鮮事物,C#、Scala等高階語言中都有類似的名稱,但意義和作用各不相同。下面就來一起認識一下Java 17中的Sealed Classes。

密封類的作用

在面嚮物件語言中,我們可以通過繼承(extend)來實現類的能力複用、擴充套件與增強。但有的時候,有些能力我們不希望被繼承了去做一些不可預知的擴充套件。所以,我們需要對繼承關係有一些限制的控制手段。而密封類的作用就是限制類的繼承

已有的限制手段

對於繼承能力的控制,Java很早就已經有一些了,主要是這兩種方式:

  1. final修飾類,這樣類就無法被繼承了
  2. package-private類(非public類),可以控制只能被同一個包下的類繼承

但很顯然,這兩種限制方式的粒度都非常粗,如果有更精細化的限制需求的話,是很難實現的。

新特性:密封類

為了進一步增強限制能力,Java 17中的密封類增加了幾個重要關鍵詞:

  • sealed:修飾類/介面,用來描述這個類/介面為密封類/介面
  • non-sealed:修飾類/介面,用來描述這個類/介面為非密封類/介面
  • permits:用在extendsimplements之後,指定可以繼承或實現的類

下面我們通過一個例子來理解這幾個關鍵詞的用法,更多Java新特性,歡迎關注Java前沿專欄,文件形式看Java新特性,閱讀學習體驗更佳,持續更新,收藏儲存!

假設我們要設計一個遊戲,這個遊戲給使用者選擇的英雄種類分為三大類:

  • 坦克
  • 輸出
  • 輔助

每個種類下又有各種不同的具體英雄。所以,從我們傳統的面向設計思路,會這樣來建立:

// 英雄基類
public class Hero {

}

// 坦克英雄的抽象
public class TankHero extends Hero {

}

// 輸出英雄的抽象
public class AttackHero extends Hero {

}

// 輔助英雄的抽象
public class SupportHero extends Hero {

}

// 坦克英雄:阿利斯塔
public class Alistar extends TankHero {

}

// 輸出英雄:伊澤瑞爾
public class Ezreal extends AttackHero {

}

// 輔助英雄:索拉卡
public class Soraka extends SupportHero {

}

整體結構有三層,具體如下圖所示:

  • 第一層:Hero是所有英雄的基類,定義英雄的基礎屬性
  • 第二層:按英雄的分類的三個不同抽象,定義同類英雄的公共屬性
  • 第三層:具體英雄的定義

這個時候,為了避免開發人員在建立新英雄的時候,搞亂這樣的三層結構。就可以通過引入密封類的特性來做限制。

假設我們希望第一、第二層是穩定的,對於第二層英雄種類的抽象不允許再增加,此時我們就可以這樣寫:

public sealed class Hero permits TankHero, AttackHero, SupportHero {

}

通過sealed關鍵詞和permitspermits關鍵來定義Hero是一個需要密封的類,並且它的子類只允許為TankHero, AttackHero, SupportHero這三個。

完成這個改造之後,我們會發現TankHero, AttackHero, SupportHero這三個類開始報錯了,具體錯誤如下:

sealed, non-sealed or final modifiers expected

這是因為父類Hero被sealed修飾之後,sealed的密封要求被傳遞過來,此時子類就必須在sealednon-sealedfinal之間選擇一個定義,它們分別代表:

  • sealed:繼續延續密封類特性,可以繼續指定繼承的類,並傳遞密封定義給子類
  • non-sealed:宣告這個類為非密封類,可以被任意繼承
  • final:不允許繼承

根據上面的假設需求,第一、第二層穩定,允許第三層具體英雄角色可以後期不斷增加新英雄,所以三類抽象英雄的定義可以這樣編寫:

public non-sealed class TankHero extends Hero {

}

而對於第三層的英雄角色,已經是最後的具體實現,則可以使用final定義來阻斷後續的繼承關係,比如這樣:

public final class Ezreal extends AttackHero {

}

通過這樣的設定,這三層英雄的結構中第一第二層就得到了比較好的保護。

好了,今天的分享就到這裡!如果您學習過程中如遇困難?可以加入我們超高質量的技術交流群,參與交流與討論,更好的學習與進步!另外,不要走開,關注我!持續更新Java新特性專欄,文件形式看Java新特性,閱讀學習體驗更佳!

歡迎關注我的公眾號:程式猿DD。第一時間瞭解前沿行業訊息、分享深度技術乾貨、獲取優質學習資源

相關文章