Kotlinのvalue class(旧称 inline class)は、単一の値を型安全にラップするための軽量なクラスです。
設計目的は明確で、
- 型安全性を高める
- 不要なオブジェクト生成を可能な限り避ける
- ドメインに意味を持たせる
という3点にあります。
基本構文
@JvmInline
value class UserId(val value: String)
重要なポイント
- 主コンストラクタ(primary constructor)で初期化される単一プロパティが必須
- JVMでは
@JvmInlineが必要 - クラスは暗黙的に
final - クラス継承は不可(ただしインターフェース実装は可能)
実行時の表現
value classは「常にインライン化される」わけではありません。
より正確に言うと
コンパイラは可能な限り underlying 値(内部プロパティ)として扱うが、状況によってはラッパーとしてボクシングされる。
ボクシングが発生する代表例
- ジェネリクス型として扱う場合
- インターフェース型として扱う場合
- nullable 型として扱う場合
例
fun <T> printValue(value: T)
ここに UserId を渡すとボクシングされます。
つまり「プリミティブ並み」と言い切るのは正確ではなく、
多くのケースで最適化されるが、常に保証されるわけではない
という理解が正解です。
data classとの違い
| 比較項目 | data class | value class |
|---|---|---|
| プロパティ数 | 複数可 | 1つのみ |
| オブジェクト生成 | 常に生成 | 状況により回避 |
| 主目的 | データ構造 | 型安全なラッパー |
| 継承 | 可 | クラス継承不可 |
value classは「構造」ではなく「意味付け」に使うものです。
equals / hashCode / toString の挙動
value classは、内部プロパティに基づいて比較されます。
UserId("abc") == UserId("abc") // true
明示的にオーバーライドしない限り、
- equals
- hashCode
- toString
は内部プロパティに委譲される形になります。
参照等価(===)は使用不可
value classは wrapper 表現と underlying 表現の両方を取り得るため、参照等価は意味を持ちません。
そのため
a === b
は使用できません。
これは仕様上の重要ポイントです。
メンバー定義について
value classでも以下が可能です。
- init ブロック
- メンバー関数
- 計算プロパティ
- 二次コンストラクタ
例
@JvmInline
value class Email(val value: String) {
init {
require(value.contains("@"))
}
fun domain(): String =
value.substringAfter("@")
}
ただし制限もある
backing fieldを持てない
メンバープロパティは backing field を持てません。
そのため
- lateinit 不可
- delegated property 不可
- 状態保持は不可
クラス継承は不可
他のクラスを継承できません。
ただし
interface Printable
@JvmInline
value class UserId(val value: String) : Printable
のようにインターフェース実装は可能です。
typealiasとの決定的な違い
typealias UserId = String
これは単なる別名であり、型は同一です。
一方
@JvmInline
value class UserId(val value: String)
これは完全に別型です。
そのため
fun getUser(id: UserId)
に String を直接渡すことはできません。
ここがvalue classの最大の価値です。
実務での活用例
ID型
- UserId
- OrderId
- ProductId
ドメイン型
- PhoneNumber
- ZipCode
単位型
- Meter
- Yen
- Kilogram
DDD(ドメイン駆動設計)との相性が非常に良く、「意味のある型」を低コストで作れるのが最大のメリットです。
まとめ
value classは
- 主コンストラクタで初期化される単一プロパティを持つ
- 可能な場合は underlying 値として扱われる
- ただし状況によりボクシングされる
- backing fieldは持てない
- クラス継承不可、インターフェース実装は可能
- 参照等価は使用不可
- equals/hashCodeは内部値に基づく
設計思想としては、
「プリミティブに意味を与えたいが、オブジェクトコストは増やしたくない」
という場面で使うものです。
以上、Kotlinのvalue classについてでした。
最後までお読みいただき、ありがとうございました。










