[Kotlin] sealed classに親しむ
目次
Kotlinの sealed class
に親しんでいきます。
sealed class
は、継承に制約を課す言語機能です。Kotlinのdocsによれば、 sealed class
として定義されたクラスのサブクラスは、すべてコンパイル時に定義されている必要があります。
Sealed classes and interfaces represent restricted class hierarchies that provide more control over inheritance. All direct subclasses of a sealed class are known at compile time. No other subclasses may appear after a module with the sealed class is compiled. For example, third-party clients can’t extend your sealed class in their code. Thus, each instance of a sealed class has a type from a limited set that is known when this class is compiled. Sealed classes | Kotlin
はじめてのKotlin sealed class
sealed class
の簡単なサンプルコードを以下に示します。親の sealed class
としてSealedClassというクラスを定義しています。また、そのサブクラス( object
)として、A、B、Cを定義しています。 sealed class
の付けられたクラスはabstractになります。そのため、SealedClass自身をインスタンス化することはできず、インスタンスとして存在し得るのは、この場合、そのサブクラスであるA、B、Cのみです。
sealed class SealedClass
object A : SealedClass()
object B : SealedClass()
object C : SealedClass()
sealed class
のサブクラスは、コンパイル時に定義されている必要があるのでした。この性質は、 when
によるパターンマッチングと相性がよく、サブクラスの型に応じた処理の分岐を容易に行うことができます。コンパイル時に全てのサブクラスがわかっているので、 when
のブランチを包括的(exhaustive)に定義することができます。IntelliJ IDEAやAndroid Studioでは、 when
のブランチがexhaustiveでない場合に警告が表示されます1。
fun main() {
val sealedClass: SealedClass = A
when (sealedClass) {
is A -> {
// do something here
}
is B -> {
// do something here
}
is C -> {
// do something here
}
}
}
なお、Kotlin 1.7以降は、exhaustiveでない when
がコンパイルエラーになることが予定されています。
また、Kotlinスタートブック(長澤(2016))などには、 sealed class
のサブクラスは sealed class
にネストされたサブクラスしか定義することができない、という記述がありました。下のサンプルコードのように、 sealed class
本体にサブクラスを定義するということです。
sealed class SealedClass {
object A : SealedClass()
object B : SealedClass()
object C : SealedClass()
}
この制限については、Kotlin 1.1 Betaにて変更が入り、必ずしもサブクラスをネストして定義する必要はなくなったようです(調べてくれた @tkhs0604
さん、ありがとうございました)。
サブクラスをネストして定義する場合は、通常、以下のサンプルコードのように、 Parent.Sub
というシンタックスでサブクラスを使います。
fun main() {
val sealedClass: SealedClass = SealedClass.A
when (sealedClass) {
is SealedClass.A -> {
// do something here
}
is SealedClass.B -> {
// do something here
}
is SealedClass.C -> {
// do something here
}
}
}
サブクラスをネストして定義した場合の方がタイプ数は多くなってしまうのですが、こちらの方がクラスの継承に規律ができて、自分は好みです。
Kotlin sealed class
に親しむ
ここからが本題です。Kotlinの sealed class
で遊びながら親しんでいきます。
コンストラクタ
まずは sealed class
のコンストラクタを作ってみます。サブクラスごとに特定の値を持つことができます。
sealed class SealedClass(val awesomeValue: Int)
object A : SealedClass(awesomeValue = 42)
object B : SealedClass(awesomeValue = 43)
object C : SealedClass(awesomeValue = 44)
val sealedClass: SealedClass = SealedClass.A
when (sealedClass) {
is A -> {
println("value is ${sealedClass.awesomeValue}") // 42
}
is B -> {
println("value is ${sealedClass.awesomeValue}") // 43
}
is C -> {
println("value is ${sealedClass.awesomeValue}") // 44
}
}
サブクラスに data class
を用いる
ここまでのサンプルコードでは、 sealed class
のサブクラスは、すべてシングルトンになっていました。お気付きのとおり、これらのサンプルコードは enum class
でもほぼ等価なコードを実装することができます。
Kotlinの sealed class
では、サブクラスにシングルトンでない通常のクラスを定義することもできるので、 enum class
と使い分けることが可能です。
以下のサンプルコードでは、 sealed class
のサブクラスとして、 data class
を定義しています。この場合は、サブクラスごとに異なるプロパティを持たせることができます。
sealed class SealedClass
data class A(val a: Int) : SealedClass()
data class B(val b: Int) : SealedClass()
val sealedClass: SealedClass = A(42)
when (sealedClass) {
is A -> {
println("value is ${sealedClass.a}")
}
is B -> {
println("value is ${sealedClass.b}")
}
}
サブクラスに class
を用いる
data class
でない通常の class
を sealed class
のサブクラスにすることも可能ですが、Android StudioやIntelliJ IDEAなどのIDEから、 equals
と hashCode
をoverrideしなさい、という警告が表示されるかと思います。個人的には、 sealed class
を使いたい場合のサブクラスは object
と data class
で事足りる場合が多い気がしています。
以下のoverrideは、IDEが自動生成したコードです。
class C : SealedClass() {
override fun equals(other: Any?): Boolean {
return this === other
}
override fun hashCode(): Int {
return System.identityHashCode(this)
}
}
abstract
なプロパティを定義する
sealed class
に abstract
なプロパティを定義することで、サブクラスが特定のプロパティを持っていることを保証できます。以下のサンプルコードでは、 sealed class
のサブクラスが必ずawesomeValueというInt型のプロパティを持ちます。
sealed class SealedClass {
abstract val awesomeValue: Int
}
data class A(override val awesomeValue: Int) : SealedClass()
data class B(override val awesomeValue: Int) : SealedClass()
val sealedClass: SealedClass = A(42)
println("value is ${sealedClass.awesomeValue}")
拡張関数を定義する
sealed class
に拡張関数を定義することもできます。
fun SealedClass.awesomeHello() = println("hello awesome world!")
sealed class
(のサブクラス)のインスタンスに対して、拡張関数を呼び出します。
val sealedClass: SealedClass = A(42)
sealedClass.awesomeHello()
Reference
- Sealed classes | Kotlin(最終アクセス日:2022年1月18日)
- KotlinのExhaustive when statementsについて - Kenji Abe - Medium(最終アクセス日:2022年1月19日)
- Kotlinスタートブック 新しいAndroidプログラミング、長澤太郎(2016)、リックテレコム
- kenken🐶さんはTwitterを使っています 「@okuzawats 気になってサンクトペテルブルクの奥へ向かってみたところ、1.1 Bataで変更が入ったようでした👀 https://t.co/ghqz8q1PPi」 / Twitter(最終アクセス日:2022年1月19日)
- Kotlin 1.1 Beta Is Here! | The Kotlin Blog(最終アクセス日:2022年1月19日)
-
Kotlin 1.6から、exhaustiveでない
when
をオプションでエラーにすることができます。Kotlin 1.7からは標準でエラーになります(参考文献2を参照)。 ↩︎
書いている人 😎

茨城県つくば市在住のモバイルアプリケーションアーキテクト(Androidが得意です)。モバイルアプリのアーキテクチャ、自動テスト、CI/CDに興味があります。いわゆる「レガシーコード」のリファクタリング・リアーキテクチャが好きです。
👉 もっと詳しく
著書 ✍
Android 依存性注入 ヒッチハイク・ガイド🧳
Androidアプリでの依存性注入(Dependency Injection)に入門するためのガイダンスです。依存性注入の概念やメリットを理解し、Dagger Hiltを用いてAndroidアプリに適用する方法を解説しています。
ソフトウェアデザイン 2023年6月号📚
特集「クリーンアーキテクチャとは何か?」の第5章「モバイルアプリ開発における実践」を執筆しました。
Android クリーンアーキテクチャ ヒッチハイク・ガイド🧳
Androidアプリでのクリーンアーキテクチャに入門するためのガイダンスです。クリーンアーキテクチャの概念を理解し、Androidアプリに適用する方法を解説しています。
Android ユニットテスト ヒッチハイク・ガイド🧳
Androidアプリのユニットテストに入門するためのガイダンスです。初学者が混乱せずにAndroidアプリのユニットテストを書き始めることができる、ということを目的としています。
Android MVVMアーキテクチャ入門🛠
Androidアプリ開発の初学者に向けた、MVVM(Model-View-ViewModel)アーキテクチャの入門書を書きました。初学者の方を確実にネクストレベルに引き上げる技術書です。NextPublishingより出版されています。
関連記事 👀
- [Kotlin] 関数定義が1つだけのinterfaceは `fun interface` で定義できる
- ランチタイムLT会 #1で「何故、UseCaseは1メソッドなのか」というLTをしました
- DroidKaigi.collect{ #1@Tokyo }で「例外を投げるな、値を返せ」というLTをしました
- [Kotlin] 末尾カンマ
- [Kotlin] ifを愛でる