4

为什么这会产生编译错误?

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 的精神。

4

3 回答 3

6

由于use { ... }不是一种语言结构,而只是一个库函数,因此编译器不知道(并且目前也没有努力证明)您传递的 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用作表达式

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

从理论上讲,实际上可以检查内联函数以仅调用一次 lambda,如果 Kotlin 编译器可以做到这一点,它将允许您的用例和其他一些用例。但这还没有实施。

于 2017-07-21T14:54:31.960 回答
-1

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

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

于 2017-07-21T13:22:04.813 回答
-1

这是因为use它是一个内联函数,这意味着 lambda 主体将内联到调用点函数,变量的实际类型myVar取决于其上下文。

如果myVarlambda 中用于读取,则类型是MyType或其超类型。例如:

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

autoClosable.use {
    myVar.todo()
}

如果myVarlambda 中用于写入,则实际类型为ObjectRef. 为什么?这是因为 Java 不允许您将变量更改出令人讨厌的类范围。实际上,myVar有效的-final。例如:

//  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是时MyType,它工作正常。例如:

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

println(myVar) // works fine

为什么kotlin 没有优化内联函数以MyType直接用于编写?

我唯一认为,编译器不知道myVar将来是否在另一个内联函数的 lambda 主体中使用。或者 kotlin 想要为所有函数保持语义一致。

于 2017-07-21T14:18:49.260 回答