首页 > 解决方案 > ExceptionInInitializerError“指定为非空的参数为空”与抽象类

问题描述

我需要计算hash硬编码图像的数量。

abstract class ImageData {
    protected abstract val master: List<String>
    val data: Iterable<HexString> = master.map { s -> hex(s) }
    val hash: Int by lazy {
        master.fold(0) { hash, s ->
            31 * hash + s.hashCode()
        }
    }
}

示例图像。

object FooImageData : ImageData() {
    override val master = listOf(
        "424d3684030000000000..."
        // ...
    )
}

例外:

java.lang.ExceptionInInitializerError
    at ....updateGraphics(Graphics.kt:162)
...
 Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter $this$collectionSizeOrDefault
    at kotlin.collections.CollectionsKt__IterablesKt.collectionSizeOrDefault(Iterables.kt)
    at ....ImageData.<init>(ImageData.kt:17)
    at ....FooImageData.<init>(FooImageData.kt:3)
    at ....FooImageData.<clinit>(FooImageData.kt:3)
    at ....updateGraphics(Graphics.kt:162)

....updateGraphics(Graphics.kt:162)是:

private suspend fun updateGraphics(...) {
    val hash = (FooImageData.hash * 31 + BarImageData.hash)

删除lazy并不能解决问题。

所有研究都表明参数的顺序可能是一个问题,但这里似乎并非如此——或者是这样吗?

使用:

abstract class ImageData {
    abstract val master: List<String>
    // Yes I know the `get()` is unnecessary but for some weird reason that causes `hash` to crash.
    val data: Iterable<HexString> get() = master.map { s -> hex(s) }
    val hash: Int by lazy {
        master.fold(0) { hash, s ->
            31 * hash + s.hashCode()
        }
    }
}

似乎解决了这个问题 - 不知道为什么。

Kotlin 版本Latest stable (1.3)

目标 JVM 版本:1.6

标签: kotlinlazy-initialization

解决方案


我认为关键的区别在于get()属性,以及抽象data的事实。master当这个基类被构造时(在创建子类之前,因为子类的构造函数必须先调用超类的构造函数),基类初始化它的所有成员。您的原始代码有这一行:

val data: Iterable<HexString> = master.map { s -> hex(s) }

这将获取 master 的值,此时它为 null,因为尚未创建具体的子类,因此还不能覆盖该属性。

在您更新的代码段中,您有以下内容:

val data: Iterable<HexString> get() = master.map { s -> hex(s) }

在抽象基类的初始化过程中,data现在不需要初始化该属性(使用 的值)。master相反,当data在运行时调用该属性时,该get函数将被执行。到那时,具体的子类已经构建并且可以为master.

文档中有更多详细信息,其中说:

因此,在设计基类时,应避免在构造函数、属性初始值设定项和 init 块中使用开放成员。

master属性是abstract,这意味着它是open。)


推荐阅读