You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose中ViewModel空指针异常问题求助

问题:Jetpack Compose ViewModel运行时触发NullPointerException

运行应用时出现如下错误:

Attempt to invoke interface method 'boolean java.util.Set.contains(java.lang.Object)' on a null object reference
java.lang.NullPointerException: Attempt to invoke interface method 'boolean java.util.Set.contains(java.lang.Object)' on a null object reference
at com.example.android.ui.GameViewModel.pickRandomWordAndShuffle(GameViewModel.kt:21)
at com.example.android.ui.GameViewModel.(GameViewModel.kt:10)

对应的ViewModel代码:

import androidx.lifecycle.ViewModel
import com.example.android.unscramble.data.allWords
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow


class GameViewModel : ViewModel() {
    private val _uiState =
        MutableStateFlow(GameUiState(currentScrambledWord = pickRandomWordAndShuffle()))
    val uiState: StateFlow<GameUiState> = _uiState.asStateFlow()

    private var _count = 0
    val count: Int
        get() = _count

    private lateinit var currentWord: String
    private var usedWords: MutableSet<String> = mutableSetOf()

    private fun shuffleCurrentWord(word: String): String {
        val tempWord = word.toCharArray()
        // Scramble the word
        tempWord.shuffle()
        while (String(tempWord) == word) {
            tempWord.shuffle()
        }
        return String(tempWord)
    }

    private fun pickRandomWordAndShuffle(): String {
        // Continue picking up a new random word until you get one that hasn't been used before
        currentWord = allWords.random()
        if (usedWords.contains(currentWord)) {
            return pickRandomWordAndShuffle()
        } else {
            usedWords.add(currentWord)
            return shuffleCurrentWord(currentWord)
        }
    }

    private fun resetGame() {
        usedWords.clear()
        _uiState.value = GameUiState(currentScrambledWord = pickRandomWordAndShuffle())
    }
    init {
        resetGame()
    }
}

代码无编译错误,需解决该运行时异常。


问题原因与修复方案

原因分析

Kotlin类的初始化顺序是先按声明顺序初始化属性,再执行init代码块。这里的核心问题:

  1. 初始化_uiState属性时,直接调用了pickRandomWordAndShuffle()方法
  2. 此时usedWords属性还未完成初始化(声明顺序在_uiState之后),导致usedWords为null,调用contains()方法触发空指针异常。

修复方案

方案一:调整属性声明顺序

将usedWords的声明移到_uiState之前,确保它先完成初始化:

class GameViewModel : ViewModel() {
    // 先声明usedWords,保证初始化顺序优先级
    private var usedWords: MutableSet<String> = mutableSetOf()
    private lateinit var currentWord: String

    private val _uiState =
        MutableStateFlow(GameUiState(currentScrambledWord = pickRandomWordAndShuffle()))
    val uiState: StateFlow<GameUiState> = _uiState.asStateFlow()

    // 其余代码保持不变
    // ...
}

方案二:延迟初始化_uiState,在init块中完成配置

把_uiState改为lateinit,在init块中通过resetGame()完成初始化,确保所有依赖属性都已准备就绪:

class GameViewModel : ViewModel() {
    private lateinit var _uiState: MutableStateFlow<GameUiState>
    val uiState: StateFlow<GameUiState>
        get() = _uiState.asStateFlow()

    private var _count = 0
    val count: Int
        get() = _count

    private lateinit var currentWord: String
    private var usedWords: MutableSet<String> = mutableSetOf()

    // 其余方法保持不变...

    init {
        resetGame()
    }

    private fun resetGame() {
        usedWords.clear()
        // 在此处初始化_uiState
        _uiState = MutableStateFlow(GameUiState(currentScrambledWord = pickRandomWordAndShuffle()))
    }
}

额外优化建议

  • 避免在属性初始化阶段调用业务逻辑方法,尽量把初始化逻辑集中到init块或专用初始化方法中,降低初始化顺序问题的概率
  • 给usedWords添加private set修饰,限制外部修改,增强代码封装性

内容的提问来源于stack exchange,提问作者Monibur Haque

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 05:45:41