Kotlinのdata classの継承について

採用はこちら

Kotlin の data class は便利ですが、継承に関しては強い制約と明確な設計意図があります。

Java の感覚のまま使うと、仕様上はコンパイルが通っても equals / hashCode / copy の挙動で事故ることがあるため、正確な理解が不可欠です。

以下、仕様 → できること / できないこと → 安全な設計パターンの順で整理します。

目次

data class の基本仕様

data class は親クラスになれない

data class User(val name: String)

class Admin(name: String) : User(name) // コンパイルエラー
  • data class暗黙的に final
  • open を付けることも不可
  • つまり 継承されることはできない

これは Kotlin の仕様として明確に定められています。

data class は「何かを継承する」ことはできる

一方で、data class 自身が 他のクラスを継承することは可能です。

abstract class Person {
    abstract val name: String
}

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

ここで重要なのは、「できる」=「安全」ではないという点です。

最重要ルール:自動生成メソッドの対象

data class では、以下のメソッドが自動生成されます。

  • equals()
  • hashCode()
  • toString()
  • copy()
  • componentN()

これらが参照するのは「primary constructor のプロパティのみ」

open class Person(open val name: String)

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

この場合

  • equals / hashCode / copy の対象
    User の primary constructor にある nameage のみ
  • 親クラス Person の状態やロジック
    一切考慮されない

つまり、

親クラスに状態があると、等価性・ハッシュ・コピーの観点で
「見えない状態」が発生する

これが data class × 継承の最大の落とし穴です。

なぜ data class は継承させられないのか?

もし data class が open で継承可能だった場合、次の問題が発生します。

  • 親 data class と子 data class で
    equals / hashCode / copy / componentN の整合性が取れない
  • 「どこまでが値として同一なのか」が曖昧になる
  • 値オブジェクトとしての性質が壊れる

そのため Kotlin は、

data class は「値」を表すための最終形クラス

という位置づけにし、継承不可(closed)にしています。

data class で「できる」継承・実装まとめ

種類可否注意点
data class を親にする不可仕様で禁止
通常クラス / 抽象クラスを継承可能親の状態は equals 等に含まれない
interface を実装推奨最も安全
sealed class の子になる王道状態表現に最適

継承したくなったときの「正解パターン」

パターン①:interface を使う(最優先)

interface Identifiable {
    val id: Long
}

data class User(
    override val id: Long,
    val name: String
) : Identifiable
  • 状態を持たない
  • data class の自動生成ロジックと衝突しない
  • 実務で最も安全

パターン②:合成(composition)を使う

data class UserInfo(
    val name: String,
    val email: String
)

data class Admin(
    val info: UserInfo,
    val role: String
)
  • 「共通部分を使い回したい」場合の正解
  • copy / equals / 分解宣言が完全に安全
  • Kotlin が強く推奨する設計

パターン③:sealed class + data class

sealed class UserState {
    data class Guest(val sessionId: String) : UserState()
    data class LoggedIn(val userId: Long, val name: String) : UserState()
}
  • UI 状態 / ドメイン状態 / Result 型で定番
  • when 式の網羅性チェックが効く
  • data class を「値」として正しく使える

よくある誤解(修正版)

「親クラスにプロパティがあると継承できない」
継承はできるが、data class の自動生成ロジックには含まれない

「data class 同士で継承できないのは equals の問題だけ」
copy / componentN / 値の意味論すべてが破綻するため

「共通フィールドがあるなら継承」
Kotlin では interface or 合成が基本

まとめ

  • data class は closed(親になれない)
  • data class は 他クラスを継承できるが注意が必要
  • 自動生成メソッドの対象は primary constructor のプロパティのみ
  • 親クラスに状態を持たせると 等価性が壊れやすい
  • 実務では
    interface / 合成 / sealed + data class が正解

以上、Kotlinのdata classの継承についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次