升级到 Kotlin 2.0 后智能转换失败

问题描述 投票:0回答:1

我使用的是 Kotlin 1.9.23,以下代码编译没有问题:

    var module: Module? = null
    for (line in lines) {
        if (line.startsWith("[")) {
            module = null

            val matcher = modulePattern.matcher(line)
            if (matcher.matches()) {
                module = Schema.modules[matcher.group(1)]
            }

            if (module == null) {
                LOG.warn { "Encountered unexpected module: $line. All subsequent values will be ignored." }
            }

            continue
        } else {
            val matcher = valuePattern.matcher(line)
            if (matcher.matches()) {
                if (module == null) {
                    LOG.warn { "Ignored property from unknown module: $line." }
                } else {
                    val tokens = matcher.group(1).split("\\.".toRegex(), 2)

                    val fieldName = tokens[0]
                    val mapKey = if (tokens.size == 2) tokens[1] else null

                    val field = Schema.fields.getValue(module)[fieldName]
                    if (field == null) {
                        LOG.warn { "Ignored unknown property \"${matcher.group(1)}\" from module ${module.name}." }
                        continue
                    }

                    val value: String = matcher.group(2)

                    if (field !is MapField) {
                        Model.setFieldValue(FieldValue(field, TypedValue.createValue(field.type, value)))
                    } else {
                        maps.computeIfAbsent(field) { arrayListOf() }.add("$mapKey=$value")
                    }
                }
            }
        }
    }

但是,将我的项目升级为使用 Kotlin 2.0 后,以下日志语句会导致问题:

LOG.warn { "Ignored unknown property \"${matcher.group(1)}\" from module ${module.name}." }

我收到编译器错误

Smart cast to 'Module' is impossible, because 'module' is a local variable that is mutated in a capturing closure.

然而问题是没有额外的线程在运行。

module
重新分配给
null
的唯一两个地方是在上面的 if 块中。

这是 Kotlin 2.0 中的错误还是有什么我不知道的事情?我对 Kotlin 很陌生。

kotlin jvm
1个回答
0
投票

编译器阻止你运行代码是正确的。根据

LOG.warn
的作用,您可能会遇到竞争条件,其中
module
实际上 could 为 null,并且您的程序将在运行时以 NullPointerException 结束。

问题是您将 lambda 传递给

LOG.warn
,而不是字符串。此 lambda 捕获变量
module
,因为您想要访问它的
name
属性。 lambda 会在稍后的某个时间,在
LOG.warn
空闲时执行。并且仅访问 then
module.name
。它可能甚至可以同时执行(*),在这种情况下,程序的其余部分将继续执行,并且它可能同时将
module
设置为空。当 lambda 现在尝试访问
name
属性时,它将抛出 NullPointerException。

有很多“可能”和“可能”,实际上这可能永远不会成为问题。但最重要的是,编译器永远无法真正“确定”这是安全的。这就是为什么它首先禁止它。嗯,显然只有新的 K2 编译器会这样做,因为旧的编译器没有抱怨。但这只是因为新编译器更智能,旧编译器也不应该允许这样做。 现在该怎么办?这完全取决于 LOG.warn 实际是什么。如果您可以控制其实现,则可以更改它:

String

 作为参数而不是 
() -> String

  • 或使
    warn
    成为内联函数。这使得编译器可以查看函数内部以确定是否存在任何恶作剧。如果 lambda 不是同时执行,它将允许在 lambda 中访问
    module.name
  • 如果您无法更改 
    LOG.warn
     那么您的选择是:

通过预先构造错误消息来访问 lambda 之外的

module.name

    val message = "Ignored unknown property \"${matcher.group(1)}\" from module ${module.name}." LOG.warn { message }
  • LOG
     的维护者提出问题并要求他们解决此问题。按照您的方式使用记录器并不罕见,因此您不应该被迫使用变通方法。在他们解决问题之前,您需要继续使用最新的 Kotlin 1.9。
  • 使用另一个不会出现此问题行为的记录器。

    
    

  • (*):大多数记录器实际上

    do

    在单独的线程中运行日志记录逻辑的某些部分,因此程序不会因记录器而减慢速度。

© www.soinside.com 2019 - 2024. All rights reserved.