我使用的是 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 很陌生。
编译器阻止你运行代码是正确的。根据
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
在单独的线程中运行日志记录逻辑的某些部分,因此程序不会因记录器而减慢速度。