编译错误:智能转换为”是不可能的,因为”是一个局部variables,
为了简化我的实际使用情况,我们假设我想在列表中find最大数字:
var max : Int? = null listOf(1, 2, 3).forEach { if (max == null || it > max) { max = it } }
但是,编译失败,出现以下错误:
智能转换为’Int’是不可能的,因为’max’是一个由变化的闭包捕获的局部variables
为什么更改的闭包会阻止智能投射在这个例子中工作?
一般情况下,当在lambda函数闭包中捕获一个可变variables时,在创建lambda之后,在lambdaexpression式和声明范围内,smart转换都不适用于该variables。
这是因为该函数可能会从其封闭的范围中逃脱,并可能稍后在不同的上下文中执行,可能多次并且可能并行执行。 作为一个例子,考虑一个假设的函数List.forEachInParallel { ... }
,它为列表的每个元素执行给定的lambda函数,但并行执行。
编译器必须生成代码,即使在严重的情况下也会保持正确,所以它不会假设variables的值在空检查后保持不变,因此不能对其进行智能转换。
但List.forEach
是有点不同,因为它是一个inline
函数。 内联函数的主体及其函数参数的主体(除非参数具有noinline
或crossinline
修饰符)被内联到调用位置,因此编译器可以crossinline
lambda中的代码作为parameter passing给内联函数,就好像它直接写在调用方法体内,使智能转换成为可能。
它可以,但目前它不,只是因为该function尚未实现。 有一个公开的问题: KT-7186 。
这看起来像一个编译器的bug。
如果forEach
中的inline lambda参数被标记为crossinline
那么由于lambdaexpression式的并发调用的可能性,我期望编译错误。
考虑以下forEach
实现:
inline fun Iterable .forEach(crossinline action: (T) -> Unit): Unit { val executorService: ExecutorService = ForkJoinPool.commonPool() val futures = map { element -> executorService.submit { action(element) } } futures.forEach { future -> future.get() } }
如果没有crossinline
修饰符,上面的实现将会失败。 没有它,lambda可能包含非本地返回 ,这意味着它不能以并发方式使用。
我建议创建一个问题: Kotlin(KT)| YouTrack 。
问题是foreach
创建了多个闭包,每个闭包访问相同的max
var
。
如果在max == null
检查之后但在it > max
之前it > max
max
被设置为null
的另一个闭包中应该发生什么?
由于每个闭包在理论上可以独立工作(可能在多个线程中),但是所有访问的访问都是相同的max
,所以不能保证在执行期间它不会改变。
感谢伊利亚对问题的详细解释! 如果你需要一个解决方法,你可以像这样使用标准for(item in list){...}
expression式:
var max : Int? = null val list = listOf(1, 2, 3) for(item in list){ if (max == null || item > max) { max = item } }