はじめに
Kotlin は「Null安全」を重視する言語です。
しかし時には「あとで初期化したい」場面もあります。
例えば:
- Androidの
Activity内でonCreate()で初期化するViewやViewModel - DI(依存性注入)であとから代入されるオブジェクト
- テストコードで後からモックを設定するフィールド
こうした「最初は未初期化、あとで代入する」パターンを安全に扱うのが
lateinit 修飾子です。
基本構文
lateinit var name: String
lateinit は 「後で初期化する」ことをコンパイラに宣言する修飾子 です。
通常の変数は宣言時に初期化が必要ですが、lateinit によってそれを遅延できます。
class Person {
lateinit var name: String
fun init() {
name = "Anna"
}
fun sayHello() {
println("Hello, $name")
}
}
fun main() {
val person = Person()
person.init()
person.sayHello() // Hello, Anna
}
普通の var との違い
var name: String // ❌ エラー: 初期化されていない
lateinit var name: String // ✅ OK(後で代入予定)
Kotlinはすべての非null変数に対して「必ず初期化」を求めます。
しかし lateinit を使うと「初期化をあとに回す」ことが許されます。
使用条件(制約)
| 条件 | 内容 |
|---|---|
✅ 修飾できるのは var(再代入可能)だけ |
val には使えない |
✅ 非null型 (String, Int, MyClass) にのみ使用可 |
String? などのnull許容型には使えない |
✅ プリミティブ型(Int, Booleanなど)には使えない |
Java互換の都合上 |
| ✅ クラスのメンバ変数・トップレベル変数にのみ使用可 | ローカル変数には使えない |
チェック方法
lateinit 変数が初期化済みかどうかを安全に確認するには:
if (::name.isInitialized) {
println("Initialized: $name")
} else {
println("Not initialized yet")
}
::nameは プロパティ参照(Property Reference) で、
.isInitializedはlateinit専用プロパティです。
未初期化のままアクセスすると…
lateinit var title: String
fun show() {
println(title) // ❌ UninitializedPropertyAccessException
}
lateinit を初期化前にアクセスすると、
UninitializedPropertyAccessException が発生します。
つまり「コンパイル時」ではなく「実行時」に例外になります。
Androidでの典型例
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
ここでは onCreate() で binding を初期化します。
このように「Activity生成時にはまだnull」「でも必ずonCreateで初期化される」
とわかっている場合、lateinit が最適です。
テストコードでの利用
class UserServiceTest {
private lateinit var userService: UserService
@Before
fun setup() {
userService = UserService()
}
@Test
fun testLogin() {
assertTrue(userService.login("Anna"))
}
}
JUnitなどのテストでは、
@Beforeメソッドで後からモックを代入するために
lateinitがよく使われます。
lazy との違い
| 比較項目 | lateinit |
lazy |
|---|---|---|
| 修飾対象 | var |
val |
| 初期化タイミング | 明示的に代入するとき | 最初のアクセス時(自動) |
| null許容型 | ❌ 非対応 | ✅ OK |
| スレッドセーフ | ❌ デフォルト非対応 | ✅ デフォルトでスレッドセーフ |
| 主な用途 | DI・View・テスト | キャッシュ・重い初期化処理 |
例:
// lateinit
lateinit var db: Database
db = Database.connect()
// lazy
val db by lazy { Database.connect() }
使いどころまとめ
| シーン | 例 | 推奨 |
|---|---|---|
| AndroidのViewBinding | lateinit var binding |
✅ |
| DI / Service注入 | lateinit var repository |
✅ |
| テストのMock | lateinit var mockService |
✅ |
| プリミティブ型の遅延初期化 | lateinit var count: Int |
❌ 不可 |
| 関数ローカル変数 | lateinit var x |
❌ 不可 |
まとめ
-
lateinitは「nullを使わずに後で初期化する」ための宣言 - 未初期化アクセスで例外が出るため、
::var.isInitializedで安全確認可能 - Androidやテストコードなど、「明確に初期化タイミングが決まっている」場面で有効
- 自動初期化やスレッドセーフを求める場合は
lazyを検討する