为什么使用块不能安全地初始化一个var?
为什么这给编译错误?
val autoClosable = MyAutoClosable() var myVar: MyType autoClosable.use { myVar= it.foo() } println(myVar) // Error: Variable 'myVar' must be initialized
也许编译器只是把{ myVar= it.foo() }
作为一个函数传递给另一个函数,并且不知道什么时候,甚至是否会被执行?
但是由于use
不仅仅是一个函数,而且Kotlin已经替代了Java的“试用资源”,关于它的一些特殊的知识将是适当的,不是吗? 现在,我不得不用一些虚拟的价值初始化myVar
,这完全不符合Kotlin的精神。
由于use { ... }
不是一个语言结构,而只是一个库函数 ,所以编译器不知道(而且目前也没有努力certificate)你传递的lambda是被执行的 。 因此禁止使用可能未被初始化的variables。
例如,比较你的代码到这个函数调用。 没有额外的代码分析,它们对于编译器是相同的:
inline fun ignoreBlock(block: () -> Unit) = Unit var myVar: MyType ignoreBlock { myVar = it.foo() } println(myVar) // Expectedly, `myVar` stays uninitialized, and the compiler prohibits it
为了绕过这个限制,你可以使用从use
返回的值(这是块返回的值)来初始化你的variables:
val myVar = autoClosable.use { it.foo() }
如果你也想处理它可能抛出的exception,那么使用try
作为expression式 :
val myVar = try { autoClosable.use { it.foo() } } catch (e: SomeException) { otherValue }
理论上,内联函数实际上可以检查一次调用一个lambda函数,如果Kotlin编译器可以这样做,它将允许您的用例和其他一些。 但是这个还没有实施。
如果在执行it.foo()
时发生exception, use
块将捕获exception,关闭你的autoClosable
,然后返回。 在这种情况下, myVar
将保持未初始化。
这就是为什么编译器不会让你做你想做的事情。
这是因为use
是内联函数 ,这意味着lambda体将内联到call-site函数,并且variablesmyVar
的实际types取决于其上下文。
如果在lambda中使用myVar
进行读取,则types为MyType
或其超types。 例如:
// v--- the actual type here is MyType var myVar: MyType = TODO() autoClosable.use { myVar.todo() }
如果 myVar
在lambda中用于写入,则实际types是ObjectRef
。 为什么? 这是因为Java不允许您将variables从恼人的类范围中更改。 实际上, myVar
是有效的 。 例如:
// v--- the actual type here is an ObjectRef type. var myVar: MyType autoClosable.use { myVar = autoClosable.foo() }
所以当编译器检查println(myVar)
,不能确定ObjectRef
的元素是否被初始化。 那么会引发编译器错误。
如果你抓住任何东西,代码也不能编译,例如:
// v--- the actual type here is an ObjectRef type. var myVar: MyType try { autoClosable.use { myVar = it.foo() } } catch(e: Throwable) { myVar = MyType() } // v--- Error: Variable 'myVar' must be initialized println(myVar)
但是,当myVar
的实际types是MyType
,它工作正常。 例如:
var myVar: MyType try { TODO() } catch(e: Throwable) { myVar = MyType() } println(myVar) // works fine
为什么 kotlin没有优化内联函数来直接使用MyType
进行书写?
我唯一想的是,编译器将来不知道myVar
是否用在另一个uninline函数的lambda体中。 或者kotlin想保持所有function的语义一致。