为什么 use 块不能安全地初始化 var?
Why can a use block not safely initialize a 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 的 try-with-resource 的替代,因此了解一些关于它的特殊知识是合适的,不是吗?现在,我被迫用一些虚拟值初始化 myVar
,这根本不符合 Kotlin 的精神。
如果在执行it.foo()
时发生异常,use
块将捕获异常,关闭您的autoClosable
,然后return。在这种情况下,myVar
将保持未初始化状态。
这就是为什么编译器不允许您做您想做的事情的原因。
这是因为use
是一个内联函数,这意味着lambda主体将被内联到调用点函数,而变量的实际类型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 不允许您将变量更改出烦人的 class 范围。事实上,myVar
是 effectively-final。例如:
// 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 想要保持所有功能的语义一致。
由于 use { ... }
不是语言构造而是 [=16=],编译器不知道(目前,也不会努力证明)您传递的 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
返回的值(这是您的块 returns 的值)来初始化您的变量:
val myVar = autoClosable.use {
it.foo()
}
如果你还想处理它可能抛出的异常,那么使用try
as an expression:
val myVar = try {
autoClosable.use {
it.foo()
}
} catch (e: SomeException) {
otherValue
}
理论上,实际上可以检查内联函数以仅调用一次 lambda,如果 Kotlin 编译器可以做到这一点,它将允许您的用例和其他一些用例。但这尚未实施。
为什么会出现编译错误?
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 的精神。
如果在执行it.foo()
时发生异常,use
块将捕获异常,关闭您的autoClosable
,然后return。在这种情况下,myVar
将保持未初始化状态。
这就是为什么编译器不允许您做您想做的事情的原因。
这是因为use
是一个内联函数,这意味着lambda主体将被内联到调用点函数,而变量的实际类型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 不允许您将变量更改出烦人的 class 范围。事实上,myVar
是 effectively-final。例如:
// 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 想要保持所有功能的语义一致。
由于 use { ... }
不是语言构造而是 [=16=],编译器不知道(目前,也不会努力证明)您传递的 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
返回的值(这是您的块 returns 的值)来初始化您的变量:
val myVar = autoClosable.use {
it.foo()
}
如果你还想处理它可能抛出的异常,那么使用try
as an expression:
val myVar = try {
autoClosable.use {
it.foo()
}
} catch (e: SomeException) {
otherValue
}
理论上,实际上可以检查内联函数以仅调用一次 lambda,如果 Kotlin 编译器可以做到这一点,它将允许您的用例和其他一些用例。但这尚未实施。