我们在Firebase上收到了kotlin方法的崩溃:

Fatal Exception: java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter code
       at [redacted].DeliveryMethod.<init>(:2)
       at [redacted].DeliveryMethodsUpdater$addSingleDMInAd$clientCall$1.invokeSuspend(DeliveryMethodsUpdater.kt:121)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
       at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
       at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)

模型是这样的:

class DeliveryMethod() {
    lateinit var code: String
    lateinit var name: String
    lateinit var description: String

var isAddressRequired: Boolean? = null
var image: JSONObject? = null
var isDefault: Boolean = false

constructor(code: String) : this() {
    this.code = code
}

constructor(code: String, name: String, description: String, image: JSONObject? = null) : this() {
    this.code = code
    this.name = name
    this.description = description
    this.image = image
}
}

方法是:

private suspend fun addSingleDMInAd(
        adId: Long,
        deliveryMethodCode: String
    ): JoinAdDeliveryMethod? {
        var addedDeliveryMethod: JoinAdDeliveryMethod? = null
        val clientCall = GlobalScope.async(Dispatchers.IO) {
            val cd = CountDownLatch(1)
            Client.getInstance().addDeliveryMethodInAd(
                adId,
                DeliveryMethod(deliveryMethodCode),
                object : NetworkCallback<JoinAdDeliveryMethod> {
                    override fun onSuccess(result: JoinAdDeliveryMethod) {
                        addedDeliveryMethod = result
                        cd.countDown()
                    }

                    override fun onFailure(err: NetworkError?) {
                        addedDeliveryMethod = null
                        cd.countDown()
                    }
                }
            )
            cd.await()
        }
        clientCall.await()
        return addedDeliveryMethod
    }

现在,我知道DeliveryMethod的构造函数被codenull值调用,但我不明白为什么这个异常只在此时出现.如您所见,方法param和以前的方法也被标记为非null.在调用DeliveryMethod的构造函数之前,是否应该抛出异常?

推荐答案

在调用DeliveryMethod的构造函数之前,是否应该抛出异常?

在Kotlin中,非null参数在运行时不可能被赋予null值(因为代码本来就不会编译).但是,如果值是从Java传递的,则可能会发生这种情况.

这就是为什么Kotlin编译器会生成空判断(在这里,您看到的内在checkNotNullParameter失败)only in non-suspend public/protected/internal methods,以防止Java误用.在private或suspend方法中这样做没有意义,因为它们只能从Kotlin(通常)调用,这会增加一些开销,这在性能敏感的代码中可能是不可接受的.

这就是为什么呼叫addSingleDMInAd本身不会因为这个错误而失败.也就是说,看看这里是如何得到空值的,这将是一件有趣的事情,因为通常公共API表面的判断就足够了.这里是否涉及一些反思或不安全的演员阵容?

此外,模型的设置方式也很奇怪.似乎lateinit是谎言,因为根据使用的构造函数,属性实际上可能根本没有设置.如果该类的用户不设置这些属性的值,则将它们标记为可为null会更安全.这样做,您甚至不需要所有的二级构造函数,只需使用默认值:

class DeliveryMethod() {
    var code: String? = null,
    var name: String? = null,
    var description: String? = null,
    var image: JSONObject? = null,
) {
    var isAddressRequired: Boolean? = null
    var isDefault: Boolean = false
}

关于addSingleDMInAd的其他值得注意的事情:

  • 在这种情况下不要使用GlobalScope.如果你需要运行短期的协同程序,为他们提供一个较小的范围,当不再需要工作时,这个范围会被取消——这样可以确保不会泄露协同程序.你可以阅读更多关于GlobalScope的潜在缺陷和可能的替代品in its own doc.也就是说,你可能根本不应该在这里开始一个合作项目,看看下一点.

  • 如果你马上使用await(),就不要使用async {}——如果你在那里等待,那么异步启动是没有意义的.如果要将上下文切换到IO,请使用withContext(Dispatchers.IO) { ... }.也就是说,您甚至不需要在这里使用IO调度程序,请参阅下一点.

  • 不要将CountDownLatch用于此目的.将异步API封装为suspend函数的正确方法是使用suspendCancellableCoroutine(查看其文档,它提供了一个如何使用它的示例).一旦使用它,就不再需要Dispatchers.IO,因为它将不再阻塞当前线程.

Kotlin相关问答推荐

如何访问方法引用的接收者?

Kotlin 函数名后面的大括号是什么意思?

同时也是一个字符串的 Kotlin 枚举

为什么使用 return instance ?: synchronized(this) { instance ?: PreferenceParameterState(context) } 时无法获得单例?

MyType.()在 Kotlin 中是什么意思?

匹配在单词边界上包含特殊字符的变量字符串的正则表达式

Kotlin 从其他类调用成员扩展函数

Kotlin:查找集合中最常见的元素

Spring webflux bean验证不起作用

为什么 Kotlin 需要函数引用语法?

@InlineOnly 注释是什么意思?

IntentService (kotlin) 的默认构造函数

是否可以在 kotlin 中嵌套数据类?

面临一些未知问题一些后端jvm内部错误

在Kotlin中传递并使用函数作为构造函数参数

Kotlin 扩展函数 - 覆盖现有方法

如何使用协调器布局和行为在CardView上完成此动画?

如何启用spring security kotlin DSL?

Kotlin替换字符串中的多个单词

Kotlin 是否支持部分应用程序?