Kotlin 通过惰性抛出 NullPointerException

Tig*_*ite 4 null exception nullpointerexception kotlin

我目前正在尝试在《Kotlin 编程大书呆子牧场指南》一书的帮助下学习 Kotlin,到目前为止一切正常。但现在我正在努力解决“惰性”初始化,它抛出一个 NullPointerException ,上面写着

无法调用“kotlin.Lazy.getValue()”,因为“< local1>”为 null

对应的行是:

val hometown by lazy { selectHometown() } 
private fun selectHometown(): String = File("data/towns.txt").readText().split("\n").shuffled().first()
Run Code Online (Sandbox Code Playgroud)

如果您想自己编译它或需要更多代码来更好地理解,我在下面提供了 Game.kt 和 Player.kt。如果为了“正常”初始化而放弃“惰性”,则家乡将按预期分配。欢迎提供任何有关解决问题和了解问题原因的提示。

// Game.kt
package com.bignerdranch.nyethack

fun main(args: Array<String>) {

    val player = Player("Madrigal")
    player.castFireball()
}

private fun printPlayerStatus(player: Player) {
    println("(Aura: ${player.auraColor()}) " + "(Blessed: ${if (player.isBlessed) "YES" else "NO"})")
    println("${player.name} ${player.formatHealthStatus()}")
}
Run Code Online (Sandbox Code Playgroud)
// Player.kt
package com.bignerdranch.nyethack

import java.io.File

class Player(_name: String, var healthPoints: Int = 100, val isBlessed: Boolean, private val isImmortal: Boolean) {


    var name = _name
        get() = "${field.capitalize()} of $hometown"
        private set(value) {
            field = value.trim()
        }

    constructor(name: String) : this(name, isBlessed = true, isImmortal = false) {
        if (name.toLowerCase() == "kar") healthPoints = 40
    }

    init {
        require(healthPoints > 0, { "healthPoints must be greater than zero." })
        require(name.isNotBlank(), { "Player must have a name" })
    }

    val hometown by lazy { selectHometown() }

    private fun selectHometown(): String = File("data/towns.txt").readText().split("\n").shuffled().first()

    fun castFireball(numFireballs: Int = 2) =
        println("A glass of Fireball springs into existence. (x$numFireballs)")


    fun auraColor(): String {
        val auraVisible = isBlessed && healthPoints > 60 || isImmortal
        return if (auraVisible) "GREEN" else "NONE"
    }
    fun formatHealthStatus() =
        when (healthPoints) {
            100 -> "is an excellent condition!"
            in 90..99 -> "has a few scratches."
            in 75..89 -> if (isBlessed) {
                "has some minor wounds but is healing quite quickly"
            } else {
                "has some minor wounds"
            }
            in 15..74 -> "looks pretty hurt"
            else -> "is in awful condition!"
        }

}
Run Code Online (Sandbox Code Playgroud)

我忘记了towns.txt,所以就在这里(并不是很重要)

Neversummer
Abelhaven
Phandoril
Tampa
Sanorith
Trell
Zan'tro
Hermi Hermi
Curlthistle Forest
Run Code Online (Sandbox Code Playgroud)

Jof*_*rey 8

当发生类似的情况时,通常是由于初始化顺序错误造成的。

类的初始化Player是这样的:

  1. name属性的支持字段已使用该_name值进行初始化
  2. init块已运行,并尝试访问name
  3. 的 gettername尝试读取hometown属性,但失败,因为hometown仍未初始化
  4. ...如果一切顺利,该hometown属性现在将使用惰性委托进行初始化

hometown所以基本上你是在配置惰性委托之前尝试访问。如果将hometown的声明移至init块上方,应该没问题。

您可以在操场上看到修复的效果

  • 不会。属性初始值设定项和“init”块按自上而下的顺序运行。任何构造函数_body_都在所有属性和`init`块**之后运行(而构造函数_arguments_在属性/init之前**评估)。 (3认同)
  • @Tignite,您可以在这里阅读有关类初始化顺序的复杂性的更多信息:https://medium.com/keepsafe-engineering/an-in-depth-look-at-kotlins-initializers-a0420fcbf546 (2认同)