1. 序章

簡単に言えば、Kotlin言語は、より安全で読みやすいコードの記述を支援するために、他の関数型言語から多くの概念を借用しました。 封印された階層は、これらの概念の1つです。

2. 封印されたクラスとは何ですか?

封印されたクラスを使用すると、型階層を修正し、開発者が新しいサブクラスを作成することを禁止できます。

これらは、可能なサブクラスの特定のセットがあり、他のサブクラスがない、非常に厳密な継承階層がある場合に役立ちます。 Kotlin 1.5以降、封印されたクラスは、同じコンパイルユニットと同じパッケージのすべてのファイルにサブクラスを持つことができます。

封印されたクラスも暗黙的にabstractです。 それらを実装できるものが他にないことを除いて、コードの残りの部分ではそれらをそのように扱う必要があります。

封印されたクラスには、抽象関数と実装された関数の両方を含む、フィールドとメソッドを定義できます。 これは、クラスの基本表現を作成し、それをサブクラスに合わせて調整できることを意味します。

2.1. 密閉されたインターフェース

Kotlin 1.5以降、インターフェースには封印された修飾子を付けることもできます。これは、クラスで機能するのと同じようにインターフェースで機能します。封印されたインターフェースのすべての実装は、コンパイル時に認識されている必要があります。

封印されたクラスに対する封印されたインターフェースの利点の1つは、複数の封印されたインターフェースから継承できることです。 Kotlinには多重継承がないため、これは封印されたクラスでは不可能です。

3. 封印されたクラスをいつ使用するのですか?

封印されたクラスは、値に対して非常に特定の可能なオプションのセットがあり、これらの各オプションが機能的に異なる場合に使用されるように設計されています。代数的データ型のみです。

一般的な使用例には、ステートマシンまたはモナディックプログラミングの実装が含まれる場合があります。これは、関数型プログラミングの概念の出現によりますます一般的になっています。

複数のオプションがあり、それらがデータの意味のみが異なる場合は、代わりに列挙型クラスを使用する方がよい場合があります。

オプションの数が不明な場合は、元のソースファイルの外部にオプションを追加できなくなるため、封印されたクラスを使用できません。

4. 封印されたクラスを書く

独自の封印されたクラスを作成することから始めましょう–そのような封印された階層の良い例はオプション Java 8から–どちらでもかまいませんいくつかまたなし。

これを実装するときは、新しい実装を作成する可能性を制限することは非常に理にかなっています。提供されている2つの実装は網羅的であり、独自の実装を追加する必要はありません。

そのため、これを実装できます。

sealed class Optional<out V> {
    // ...
    abstract fun isPresent(): Boolean
}

data class Some<out V>(val value: V) : Optional<V>() {
    // ...
    override fun isPresent(): Boolean = true
}

class None<out V> : Optional<V>() {
    // ...
    override fun isPresent(): Boolean = false
}

これで、次のインスタンスがあるときはいつでも保証できます。 オプション 、私たちは実際にどちらかを持っていますいくつかまたはなし

Java 8では、封印されたクラスがないため、実際の実装は異なって見えます。

次に、これを計算に利用できます。

val result: Optional<String> = divide(1, 0)
println(result.isPresent())
if (result is Some) {
    println(result.value)
}

最初の行は、SomeまたはNoneのいずれかを返します。 次に、結果が得られたかどうかを出力します。

5. いつで使用する

Kotlinは、whenコンストラクトで封印されたクラスを使用することをサポートしています。 可能なサブクラスの正確なセットが常に存在するため、コンパイラーは、列挙の場合とまったく同じ方法で、ブランチが処理されない場合に警告を発することができます。

つまり、このような状況では、通常、キャッチオールハンドラーは必要ありません。つまり、新しいサブクラスを追加することは自動的に安全です。コンパイラーは、処理していない場合はすぐに警告を発し、必要になります。続行する前にそのようなエラーを修正します。

上記の例を拡張して、返されたタイプに応じてエラーの結果を出力することができます。

val message = when (result) {
    is Some -> "Answer: ${result.value}"
    is None -> "No result"
}
println(message)

2つのブランチのいずれかが欠落している場合、これはコンパイルされず、代わりに次のエラーが発生します。

'when' expression must be exhaustive, add necessary 'else' branch

6. 概要

封印されたクラスは、API設計ツールボックスにとって非常に貴重なツールになる可能性があります。 予想されるクラスのセットの1つにしかなり得ない、よく知られた構造化されたクラス階層を許可すると、コードから潜在的なエラー状態のセット全体を削除すると同時に、読みやすく、保守しやすくなります。

いつものように、コードスニペットはGitHubにあります。