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は 暗黙的に finalopenを付けることも不可- つまり 継承されることはできない
これは 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 にあるnameとageのみ- 親クラス
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の継承についてでした。
最後までお読みいただき、ありがとうございました。










