13. コード例 (冒頭に出てきた data class を value class に移植)
@JvmInline // JVM バックエンドをターゲットにする場合のみ必要
value class MailAddress(private val text: String) {
companion object {
private val PATTERN = "(メールアドレスの正規表現 )".toRegex()
}
init {
if (!text.matches(PATTERN)) throw Exception("「${text}」は不正な値です。")
}
}
基本的には `data` を `value` に書き換えばOK (簡単!)
14. value class は Kotlin1.5 で stable になった新機能
機能概要
● 一言でいえば、data class のサブセット。以下の制約がある
○ 単一のプロパティしか持てない
○ mutableなプロパティを持てない ( var を使ったプロパティが宣言できない )
○ 参照の比較はできない ( === を使った比較ができない )
● value class に依存したメソッド名はメソッド名がマングリングされる
@JvmInline
value class UInt(val x: Int)
// コンパイル後、関数は compute-<hashcode>(int x) となる (<hashcode> は7桁の英数字になる)
fun compute(x: UInt) { }
参考: https://kotlinlang.org/docs/inline-classes.html / https://star-zero.medium.com/kotlin%E3%81%AEvalue-class-27e865696f35
15. value class は Kotlin1.5 で stable になった新機能
機能概要 (続き)
● コンパイル時にできる限りプリミティブ型としてバイトコードを生成するので data classより高速
○ 但し以下のような場合はプリミティブ型への最適化はされない
@JvmInline
value class Foo(val i: Int) : I
fun <T> asGeneric(x: T) {} // ジェネリクスとして引数を設定する場合
fun asInterface(i: I) {} // インターフェイスとして引数を設定する場合
fun asNullable(i: Foo?) {} // Nullableなプロパティとして引数を設定する場合
val f = Foo(42)
asGeneric(f)
asInterface(f)
asNullable(f)
参考: https://kotlinlang.org/docs/inline-classes.html / https://star-zero.medium.com/kotlin%E3%81%AEvalue-class-27e865696f35
18. (注意点1) JSONシリアライズ
例えば、jackson を使用して以下の User クラスをシリアライズすると
プロパティ名がマングルされてしまいます。
// kotlin
value class UserID(val userId: String)
value class UserName(val userName: String)
data class User(val userId: UserID, val name: UserName)
// JSON
// 注意: プロパティ名がマングルされている
{
"userId-abc1234": "abc123",
"name-def5678": "foo"
}
20. (注意点2) mockito が対応していない
value class を使用したオブジェクトをmockitoで使用できません。
value class UserID(val userId: String)
// モック
val someClass = Mockito.mock(SomeClass::class.java)
Mockito.doNothing().`when`(someClass.doSomething(isA(UserID::class.java)))
fun <T> isA(type: Class<T>): T {
Mockito.isA(type)
return null as T
}
// エラーメッセージ
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "UserID.unbox-impl()"
because the return value of "isA(java.lang.Class)" is null
21. (注意点2) mockito が対応していない
解決策としては mockk を使用すれば良い (GitHub issue)
value class UserID(val userId: String)
// モック
val someClass = mockk<SomeClass>()
every {
someClass.doSomething(UserID(any()))
}