为什么使用块不能安全地初始化var?

时间:2017-07-21 13:19:30

标签: kotlin autocloseable

为什么会出现编译错误?

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的try-with-resource,因此对它的一些特殊知识是合适的,不是吗?现在,我被迫用一些虚拟值初始化myVar,这根本不符合Kotlin的精神。

3 个答案:

答案 0 :(得分:6)

由于use { ... }不是语言构造而是is just a library function,编译器不知道(并且,目前,不会努力证明)你传递的lambda是曾经执行过。因此,禁止使用可能未初始化的变量。

例如,将您的代码与此函数调用进行比较。如果没有额外的代码分析,它们对于编译器来说是相同的:

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返回的值(这是您的块返回的值)来初始化变量:

val myVar = autoClosable.use {
    it.foo()
}

如果您还想处理它可能抛出的异常,请使用try as an expression

val myVar = try {
    autoClosable.use {
        it.foo()
    }
} catch (e: SomeException) {
    otherValue   
}

理论上,实际上可以检查内联函数以仅调用一次lambda,如果Kotlin编译器可以这样做,它将允许你的用例和其他一些。但这尚未实施。

答案 1 :(得分:-1)

如果在执行it.foo()时发生异常,use块将捕获异常,关闭autoClosable,然后返回。在这种情况下,myVar将保持未初始化状态。

这就是为什么编译器不会让你做你想做的事情。

答案 2 :(得分:-1)

这是因为use是一个内联函数,这意味着lambda body将内联到call-site函数,以及变量的实际类型myVar取决于其背景。

IF myVar在lambda中用于读取,类型为MyType或其超类型。例如:

//      v--- the actual type here is MyType
var myVar: MyType = TODO()

autoClosable.use {
    myVar.todo()
}

IF myVar在lambda中用于写入,实际类型为ObjectRef。为什么?这是因为Java不允许您将变量更改为烦人的类范围。事实上,myVar 有效最终。例如:

//  v--- the actual type here is an ObjectRef type.
var myVar: MyType

autoClosable.use {
    myVar = autoClosable.foo()
}

所以当编译器在println(myVar)检查时,它无法确定ObjectRef的元素是否已初始化。然后引发编译器错误。

IF 你抓住任何东西,代码也无法编译,例如:

//  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的实际类型为MyType时,它可以正常工作。例如:

var myVar: MyType
try {
    TODO()
} catch(e: Throwable) {
    myVar = MyType()
}

println(myVar) // works fine

为什么 kotlin没有优化内联函数来直接使用MyType进行编写?

我认为唯一的一点是,编译器不知道myVar是否会在未来的另一个uninline函数的lambda体中使用。或者kotlin希望保持所有功能的语义一致。

相关问题