懒惰的 Kotlin 抛出 NullPointerException

Posted

技术标签:

【中文标题】懒惰的 Kotlin 抛出 NullPointerException【英文标题】:Kotlin by lazy throws NullPointerException 【发布时间】:2021-08-25 15:35:08 【问题描述】:

我目前正在尝试借助“Kotlin Programming The Big Nerd Ranch Guide”一书来学习 Kotlin,到目前为止一切正常。 但是现在我正在为“懒惰”的初始化而苦苦挣扎,它会抛出一个 NullPointerException,它说

无法调用“kotlin.Lazy.getValue()”,因为“”为空

对应的行是:

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

如果您想自己编译或需要更多代码以便更好地理解,我会在下面提供 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()")

// 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!"
        


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

Neversummer
Abelhaven
Phandoril
Tampa
Sanorith
Trell
Zan'tro
Hermi Hermi
Curlthistle Forest

【问题讨论】:

【参考方案1】:

当发生这种情况时,通常是由于初始化顺序错误。

Player 类的初始化是这样进行的:

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

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

您可以看到正在运行的修复on the playground

【讨论】:

我一回到家就试试你的建议。但是无论代码放在类中的哪个位置,init都不会在构造后立即执行吗? 没有。属性初始化器和init 块以自上而下的顺序运行。任何构造函数 body 运行 after 所有属性和 init 块(而构造函数 arguments 被评估 before properties/init )。 原因已确认。 (我在您发布此内容之前发现了问题:-)  @Tignite 创建minimal, reproducible example 对您来说是一个很好的练习(同时也为您节省了所有读者的麻烦);这样做会告诉你问题出在哪里以及你需要什么来触发它。 (我把它缩减为大约 10 行非空白代码。) @Tignite 你可以在这里阅读更多关于类初始化顺序的复杂性:medium.com/keepsafe-engineering/… @gidds 在发布之前,我实际上在 play.kotlinlang.org 上测试了修复 :) 我不确定 100% 的原因是因为在操场上,出于安全原因,文件访问失败:D 现在我替换了用一个哑字符串,我确认它有效

以上是关于懒惰的 Kotlin 抛出 NullPointerException的主要内容,如果未能解决你的问题,请参考以下文章

null 不能强制转换为非 null 类型 kotlin.String NullPointer 异常

Kotlin var懒惰的热量

如何在 Kotlin 中无限懒惰地循环列表?

nullpointer遍历xml

使用PrimeFaces自动完成获取nullpointer异常

在 Kotlin 中将值从 Activity 传递到 Fragment