Kotlin - lateinit
Kotlin lateinit 使用陷阱案例記錄
問題描述
在使用 lateinit 時,由於其特性會強制變數為非空值,導致了一個常見的邏輯錯誤:
- 開發時錯誤地假設某個值一定會被初始化
- 實際運行時發現某些情況下該值可能不存在
- 結果造成 UninitializedPropertyAccessException 異常
問題示例
假設我們有以下程式碼:
class UserProfile {
lateinit var nickname: String
fun initializeUser(data: UserData) {
// 假設 nickname 一定會在 UserData 中
nickname = data.nickname // 可能會有 UninitializedPropertyAccessException
}
fun displayNickname() {
println("User nickname: $nickname")
}
}
為什麼會出問題?
- lateinit 的特性:
- 變數必須是非空類型
- 使用前必須初始化
- 沒有初始化就使用會拋出異常
- 錯誤的假設:
- 開發時誤以為某個值一定會存在
- 沒有考慮到所有可能的業務場景
- 缺乏適當的空值處理機制
解決方案
方案 1:使用可空類型代替 lateinit
class UserProfile {
private var nickname: String? = null
fun initializeUser(data: UserData) {
nickname = data.nickname
}
fun displayNickname() {
println("User nickname: ${nickname ?: "Not set"}")
}
}
方案 2:使用 by lazy 延遲初始化
class UserProfile {
private val nickname by lazy {
// 提供預設值或處理邏輯
getUserNickname() ?: "Default"
}
}
方案 3:保留 lateinit 但加入檢查機制
class UserProfile {
lateinit var nickname: String
fun initializeUser(data: UserData) {
if (data.hasNickname()) {
nickname = data.nickname
}
}
fun displayNickname() {
if (::nickname.isInitialized) {
println("User nickname: $nickname")
} else {
println("Nickname not set")
}
}
}
最佳實踐建議
- 使用 lateinit 前先評估:
- 該值是否真的必須存在?
- 是否有可能出現空值的情況?
- 使用可空類型是否更合適?
- 防禦性程式設計:
- 加入 isInitialized 檢查
- 提供預設值或錯誤處理機制
- 明確記錄初始化的時機和條件
- 文件化:
- 註明為什麼使用 lateinit
- 記錄初始化的責任歸屬
- 說明可能的異常情況
- 替代方案考慮:
- 使用可空類型(String?)
- 使用 by lazy 委託
- 使用其他設計模式(如 Builder)
經驗總結
- lateinit 應該只用在確實能保證初始化的場景
- 在不確定的情況下,優先使用可空類型
- 需要延遲初始化時,考慮使用 by lazy
- 重要的業務邏輯要加入防禦性檢查機制