LoginSignup
5
2

More than 1 year has passed since last update.

value classの挙動

Posted at

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
5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2