'lateinit' modifier is not allowed on properties of primitive type はなぜ起こるのか。
背景
- なんとなくnullableにしたくなくて次のように記載したら'lateinit' modifier is not allowed on properties of primitive type と警告された。
/** * Constructs an [ClientException] with no detail message. */ constructor() : super() /** * Constructs an [ClientException] with the specified detail message. * @param message the detail message. */ constructor(message: kotlin.String, statusCode: Int) : super(message) { status = statusCode } private lateinit var status: Int
実際この場合ではどうしてもnullが発生しうるパターンだったので、lateinit使わずに以下のように表現すべきだった。
private var status: Int?
けどもなんでプリミティブ型はlateInitできないんだっけ?と思ったので調べてみた。
リファレンスを読んでみる
公式リファレンスを参照
lateInitが用意された理由は「そもそもlateInitないと、例えばテストコードのようなコンストラクタでないところで初期化せざるを得ないとき、毎回nullチェックするのだるい」ってことらしい
public class MyTest { lateinit var subject: TestSubject @SetUp fun setup() { subject = TestSubject() } @Test fun test() { subject.method() // dereference directly } }
しかし、「プリミティブ型にはlateInit使えないよ」とは書いてあったものの、なぜ使えないのかについては記載がなかった。
おんなじ疑問を持った人がいた。
ここの回答に以下のような回答があった。
kotlinはnullを利用してlateinitプロパティが初期化されていないことをマークし、そのプロパティにアクセスした時に適切な例外をスローする。 プリミティブ型の場合そのような値がないためlateInitが判断できない。
え、Int型ってオブジェクトだからnull持てるのになんでって思った。
ソースコードを見てみる
そもそもInt型のようなプリミティブ型ラッパークラスについてあんまりよく理解していなかったのでInt型クラスを開いてみる
/** * Represents a 32-bit signed integer. * On the JVM, non-nullable values of this type are represented as values of the primitive type `int`. */ public class Int private constructor() : Number(), Comparable<Int> {
このクラスはJVM上ではintとして表現されるよって書いてある。そうだよね。JVM上で動作するんだからkotlinでいくらオブジェクト型だろうとintになったらnull持てないよね。。 なのでStringにはlateInit使えるけどIntやLongでは使えないのか。
まとめ
- kotlinのInt型はJVM上でintとして扱われる
- lateinitはプロパティが初期化されていないことをnullを用いて判断している
- なのでJVM上nullを持てない型にはlateinitが利用できない
調べている場合ではない時ほどこういうの気になってしまうのはよくないと思いました。