Kotlin データクラスとシールドクラス


データクラス

Kotlin ではデータのみを含むクラスを作成できます。キーワードはdata:

data class User(val name: String, val age: Int)

コンパイラはプライマリコンストラクタ内で宣言されたすべてのプロパティから自動的に以下の関数を抽出します:

  • equals() / hashCode()
  • toString()形式は次のようになります:"User(name=John, age=42)"
  • componentN() functionsプロパティに対応し、宣言順に並びます。
  • copy()関数

これらの関数がクラス内で既に明示的に定義されているか、スーパークラスから継承されている場合は、生成されません。

生成されるコードの一貫性と意味を保証するために、データクラスは以下の条件を満たす必要があります:

  • プライマリコンストラクタは少なくとも1つのパラメータを含む必要があります。

  • すべてのプライマリコンストラクタのパラメータは、次のようにマークする必要があります:valまたはvar ;

  • データクラスは次のように宣言することはできません:abstract, open, sealedまたはinner;

  • データクラスは他のクラスを継承できません(ただし、インターフェースを実装することはできます)。

コピー

コピーには copy() 関数を使用します。この関数を使用してオブジェクトをコピーし、一部のプロパティを変更できます。上記の User クラスでは、その実装は次のようになります:

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)

インスタンス

copy 関数を使用して User データクラスをコピーし、age プロパティを変更します:

data class User(val name: String, val age: Int)


fun main(args: Array<String>) {
    val jack = User(name = "Jack", age = 1)
    val olderJack = jack.copy(age = 2)
    println(jack)
    println(olderJack)

}

出力結果は次のとおりです:

User(name=Jack, age=1)
User(name=Jack, age=2)

データクラスと分解宣言

コンポーネント関数により、データクラスを分解宣言で使用できます:

val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"

標準データクラス

標準ライブラリは次のものを提供します:PairとTriple。ほとんどの場合、名前付きデータクラスの方が良い設計選択です。コードの可読性が高まり、意味のある名前とプロパティが提供されるからです。


シールドクラス

シールドクラスは、制限されたクラス継承構造を表すために使用されます:値が有限の種類のいずれかであり、他の型を持つことができない場合。ある意味で、それらは列挙クラスの拡張です:列挙型の値の集合も制限されていますが、各列挙定数は1つのインスタンスしか存在しません。一方、シールドクラスのサブクラスは、状態を含むことができる複数のインスタンスを持つことができます。

シールドクラスを宣言するには、次を使用します:sealedクラスを修飾します。シールドクラスはサブクラスを持つことができますが、すべてのサブクラスはシールドクラス内にネストされている必要があります。

sealed は interface や abstract class を修飾できません(警告は出ますが、コンパイルエラーにはなりません)。

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

fun eval(expr: Expr): Double = when (expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
}

シールドクラスを使用する主な利点は、when 式を使用する際に、ステートメントがすべてのケースをカバーしていることを検証できれば、そのステートメントに else 節を追加する必要がないことです。

fun eval(expr: Expr): Double = when(expr) {
    is Expr.Const -> expr.number
    is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
    Expr.NotANumber -> Double.NaN
    // 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}
その他の拡張