Kotlin / Programming

ザクっと理解するKotlin 密閉クラス(Sealed classes)と密閉インターフェース(Sealed interface)

密閉クラスと密閉インターフェースとは

密閉クラスと密閉インターフェースは継承において強い力を持っています。
密閉クラスの直接のサブクラスは、同じパッケージまたはモジュール内で宣言する必要があります。
他のパッケージやモジュールからは、その密閉クラスのサブクラスを継承することはできません。
例えば、サードパーティのコードの密閉クラスを拡張することはできません。

密閉インターフェースも同様に、密閉インターフェースがあるモジュール以外ではサブクラスを宣言できません。

密閉クラスとenumクラスは似ている点があります。
列挙型の値も制限されますが、列挙定数は個々に単一のインスタンスとして存在します。
一方、密閉クラスのサブクラスは複数のインスタンスを持つことができ、それぞれが独自の状態を持つことができます。

例としてライブラリのAPIを考えてみます。
ライブラリは投げる可能性のあるエラーを処理するためのエラークラスを含むことがあります。
もしエラークラスの階層に、パブリックなAPIで可視なインターフェースや抽象クラスが含まれている場合、ユーザーが実装または拡張することが可能です。
しかし、ライブラリは外部で宣言されたエラーを知ることはできないため、自身のクラスと一貫して処理することはできません。
密閉クラスを使用することで、ライブラリの作者はすべての可能なエラータイプを把握しており、後から他のタイプが現れることはありません。

密閉クラスとインターフェースの宣言

sealedキーワードを名前の前につけることで宣言できます。

sealed interface Error

sealed class IOError(): Error

class FileReadError(val file: File): IOError()
class DatabaseError(val source: DataSource): IOError()

object RuntimeError : Error

密閉クラスは抽象クラスであり、抽象メンバーを持つことはできますが、直接インスタンス化することはできません。

コンストラクタの可視性はprotected(デフォルト)かprivateのみしか設定できません。

直接のサブクラスの宣言場所

密閉クラスやインターフェースとその直接のサブクラスは同じパッケージ内で宣言される必要があります。
サブクラスは、Kotlinの継承のルールに則っていれば、どの可視性を持っていても問題ありません。

enumクラスは密閉クラスを継承できませんが、密閉インターフェースは可能です。

これらの制限は間接的なサブクラスには当てはまりません。
密閉クラスの直接のサブクラスにsealedキーワードがついていなければ、これらの制限はありません。

sealed interface Error // 同パッケージ、モジュール内でのみこれを継承可能

sealed class IOError(): Error // 同パッケージ、モジュール内のため継承可能
open class CustomError(): Error // 参照できるのならどこからでも継承可能

密閉クラスとwhenキーワード

密閉クラスを使用するメリットはwhenキーワードを使用する際に現れます。
条件が全てのケースをカバーしている場合、elseキーワードが不要になります。

fun log(e: Error) = when(e) {
    is FileReadError -> { println("Error while reading file ${e.file}") }
    is DatabaseError -> { println("Error while reading from database ${e.source}") }
    is RuntimeError ->  { println("Runtime error") }
    // 全てのケースを網羅しているためelseキーワードが不要
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です