在Java 15的時候出現了sealed class / interface的預覽功能,從Java 17開始sealed成為正式的新功能。有些人將sealed翻譯為彌封類別,或是密封類別,本文中以原文sealed來稱呼。sealed是用來限制類別繼承與介面實作用的。一個Java Class的可用性有四種狀態,分別是public、protected、(無標示)、private,但可否繼承的設定卻只有兩種,分別是無標示與final兩個等級。要不就是都可以繼承,要不就是都不能繼承,沒辦法選擇只有指定Class可以繼承,這就是sealed出現來要解決的問題。
概念說明
本次新增多了幾個關鍵字,分別是sealed
、non-sealed
、permits
。sealed用來標注一個類別或介面成為sealed,被標為sealed的類別或介面一定要在後面加上permits關鍵字來指定哪些類別或介面可以實作或繼承。
繼承Sealed Class或實作Sealed Interface的子類別/子介面(以下只稱呼子類別)必須標註要如何處理原本的Sealed狀態。有三種選擇,第一個是在子類別的前面加上sealed
,讓子類別繼續限制繼續限制只能被permits
指定的類別繼承。第二種可以加上non-sealed
代表變回普通的類別,可以被任何類別繼承。最後是加上原本就有的final
關鍵字,跟之前一樣代表不能被任何類別繼承的最終類別。不能什麼都不寫,會跳出通知說:
The class Penguin with a sealed direct superclass or a sealed direct superinterface Bird should be declared either final, sealed, or non-sealed
下方重新整理一下。
Sealed Interface
Sealed Interface要透過permits
關鍵字指定哪些Interface可以繼承自己,或是哪些Class可以實作自己。繼承Sealed Interface要指定自己是Sealed Interface還是Non-sealed Interface。
繼承的Interface選擇sealed interface
又會跟回到上面的狀態,要重新指定可以繼承或實作自己的人選;選擇non-sealed interface
就會變回普通的Interface,可以被其他Interface繼承或是被Class實作。
實作Sealed Interface的Class要指定自己是Sealed Class、Non-sealed Class、Final Class,差別可以看下一小節說明。
Sealed Class
Sealed Class要透過permits
關鍵字指定哪些Class可以繼承自己,繼承Sealed Class的Class要指定自己是Sealed Class、Non-sealed Class、Final Class。
選擇sealed class
的狀況就要重複上面說明的那樣;選擇non-sealed class
後就會變回普通的Class,可以被其他Class繼承;選擇final class
代表不能被任何Class繼承。
範例說明
以下類別與介面都沒加上public
、protected
、private
等修飾關鍵字,只是為了方便測試與觀看,實際使用時依然能夠指定這些關鍵字。
Sealed Interface 動物
首先我們建立一個Animal(動物)介面,是一個sealed interface,並且設定只有Chordata(脊索動物)類別可以實作此介面。
/**
* klab.tw
*/
sealed interface Animal permits Chordata {
void eat();
}
補充一下,sealed interface的permits可以寫class也可以寫interface,而一個interface繼承一個sealed interface的時候只能選擇是sealed
還是non-sealed
,畢竟final interface是沒有意義的… 雖然Java 8給interface新增default method之後,一個不能被繼承或實作的interface的似乎還是有用啦。
Sealed Class 脊索動物
建立一個Chordata類別,實作了Animal介面的eat()
方法,然後設定只有Bird(鳥)類別可以繼承Animal。
sealed class Chordata implements Animal permits Bird {
public void eat() {
System.out.printf("我是%s,我在吃飯。%n",
getClass().getSimpleName());
}
}
Sealed Class 鳥
再來是Bird類別,我們設定只有Parrot(鸚鵡)與Penguin(企鵝)可以繼承Bird。大部分鳥類會飛,我們添增一個fly()
方法。
sealed class Bird extends Chordata permits Parrot, Penguin {
public void fly() {
System.out.printf("我是%s,我在飛翔。%n",
getClass().getSimpleName());
}
}
Non-Sealed Class 鸚鵡
我們建立Parrot(鸚鵡)類別,並且標註為non-sealed class,代表變成普通的Java Class可以被任何類別繼承。不是所有種類的鸚鵡都會講話,但通常都滿會唱歌,我們新增一個sign()
方法。
non-sealed class Parrot extends Bird {
public void sing() {
System.out.printf("我是%s,我在唱歌。%n",
getClass().getSimpleName());
}
}
Nomarl Class 灰鸚鵡
建立GrayParrot(灰鸚鵡)類別繼承Parrot。因為剛才的Parrot已經被標註為non-sealed
了,變回普通的Java Class,因此任何類別都可以繼承Parrot。灰鸚鵡是一種很會講話的鸚鵡,因此增加一個talk()
方法。
class GrayParrot extends Parrot {
public void talk() {
System.out.printf("我是%s,我在說話。%n",
getClass().getSimpleName());
}
}
Final Class 企鵝
現在建立一個Penguin(企鵝)類別,繼承了Bird(鳥)類別,使用了final class
修飾代表Penguin不能再被任何類別繼承了。因為企鵝不會飛,因此Override(覆寫)了fly()
方法,改成不會飛。然後再為會游泳的企鵝加上swim()
方法。
final class Penguin extends Bird {
@Override
public void fly() {
System.out.printf("我是%s,我不會飛。%n",
getClass().getSimpleName());
}
public void swim() {
System.out.printf("我是%s,我在游泳。%n",
getClass().getSimpleName());
}
}
使用範例
// https://klab.tw/2023/01/java-17-sealed-non-sealed-and-final-class/
var chor = new Chordata();
chor.eat(); // 我是Chordata,我在吃飯。
var bird = new Bird();
bird.eat(); // 我是Bird,我在吃飯。
bird.fly(); // 我是Bird,我在飛翔。
var part = new Parrot();
part.eat(); // 我是Parrot,我在吃飯。
part.fly(); // 我是Parrot,我在飛翔。
part.sing(); // 我是Parrot,我在唱歌。
var gray = new GrayParrot();
gray.eat(); // 我是GrayParrot,我在吃飯。
gray.fly(); // 我是GrayParrot,我在飛翔。
gray.sing(); // 我是GrayParrot,我在唱歌。
gray.talk(); // 我是GrayParrot,我在說話。
var peng = new Penguin();
peng.eat(); // 我是Penguin,我在吃飯。
peng.fly(); // 我是Penguin,我不會飛。
peng.swim(); // 我是Penguin,我在游泳。