value classの内部でcompanion objectを使う場合に、classやdata classと挙動が違ったので注意が必要
まとめ
- value classでは class→companion objectの順でinitが実行される
- class ではcompanion object→classの順でinitが実行される
- そのためvalue classのcompanion objectで定義した値をclassのinit内で使うとNullPointerExceptionが発火する
挙動
まずは違いを知るためにclassの場合の挙動
コード
class T(val value: Float) {
companion object {
init {
println("companion object init")
}
val N = T(0.0f)
val range = 0f..1f
}
init {
println("class init")
}
}
fun main() {
println("main range=${T.range}")
}
実行結果
companion object init
class init
main range=0.0..1.0
companion objectのinitが実行されてからclassのinitが実行される。
次にvalue classの場合
コード
@JvmInline
value class T(val value: Float) {
companion object {
init {
println("companion object init")
}
val N = T(0.0f) // 定義を追加
val range = 0f..1f
}
init {
println("class init")
}
}
fun main() {
println("main range=${T.range}")
}
実行結果
class init
companion object init
main range=0.0..1.0
この場合、なんとclass実装と違ってvalue classのinitが実行されてからcompanion objectのinitが実行される。
そのため、以下のようにcompanion object内で定数を定義し、value classのinitで値のチェックを行うようにするとエラーとなる
コード
@JvmInline
value class T(val value: Float) {
companion object {
val N = T(0.0f)
private val range = 0f..1f
}
init {
check(value in range)
}
}
fun main() {
println(T.N)
}
出るException
Exception in thread "main" java.lang.ExceptionInInitializerError
at FileKt.main (File.kt:14)
at FileKt.main (File.kt:-1)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (:-2)
Caused by: java.lang.NullPointerException
at T .constructor-impl(File.kt:9)
at T .<clinit>(File.kt:4)
at FileKt .main(File.kt:14)
他にも面白い動きがあり、value classのcompanion object内の定数にアクセスするだけだとinitは呼ばれない
コード
@JvmInline
value class T(val value: Float) {
companion object {
init {
println("companion object init")
}
val range = 0f..1f
}
init {
println("class init")
}
}
fun main() {
println("main range=${T.range}")
}
結果
main range=0.0..1.0
この場合、classのinitすら呼ばれない
同じ用にdata classで実行するとrangにアクセスするだけでcompanion objectのinitが実行される
コード
data class T(val value: Float) {
companion object {
init {
println("companion object init")
}
val range = 0f..1f
}
init {
println("class init")
}
}
fun main() {
println("main range=${T.range}")
}
companion object init
main range=0.0..1.0