【问题标题】:Why the variable can't be initialized correctly in inline function as in java?为什么变量不能像在 java 中那样在内联函数中正确初始化?
【发布时间】:2017-07-21 17:15:18
【问题描述】:

我们知道 lambda 主体是惰性的,因为如果我们不调用 lambda,则永远不会调用 lambda 主体中的代码。

我们还知道在任何函数语言中,即使没有初始化变量也可以在函数/lambda中使用,例如javascript、ruby、groovy和.etc,例如,下面的groovy代码可以正常工作:

def foo

def lambda = { foo }

foo = "bar"

println(lambda())
//      ^--- return "bar"

我们还知道,如果在 Java 中的 try-block 中引发 Exception 时 catch-block 已经初始化了变量,我们可以访问未初始化的变量,例如:

//  v--- m is not initialized yet
int m;

try{ throw new RuntimeException(); } catch(Exception ex){ m = 2;}

System.out.println(m);// println 2

如果 lambda 是惰性的,为什么 Kotlin 不能在 lambda 中使用未初始化的变量?我知道 Kotlin 是一种空安全语言,因此编译器会从上到下分析代码,包括 lambda 主体,以确保变量已初始化。所以 lambda 主体在编译时不是“懒惰”的。例如:

var a:Int
val lambda = { a }// lambda is never be invoked
//             ^--- a compile error thrown: variable is not initialized yet
a = 2

:但是为什么下面的代码也不能工作?我不明白,因为变量在 Java 中是有效最终,如果你想更改变量值,你必须使用ObjectRef,而这个测试与我之前的结论相矛盾:" lambda body 在编译时并不懒惰”。例如:

var a:Int
run{ a = 2 }// a is initialized & inlined to callsite function

//      v--- a compile error thrown: variable is not initialized yet
println(a)

所以我只能认为编译器无法确定ObjectRef 中的element 字段是否已初始化,但@hotkey 否认了我的想法。 为什么

:为什么 Kotlin 内联函数不能正常工作,即使我像在 java 中一样在 catch-block 中初始化变量?例如:

var a: Int

try {
    run { a = 2 }
} catch(ex: Throwable) {
    a = 3
}

//      v--- Error: `a` is not initialized
println(a)

但是,@hotkey 已经提到你应该在 Kotlin 中使用 try-catch 表达式来初始化 his answer 中的变量,例如:

var a: Int = try {
    run { 2 }
} catch(ex: Throwable) {
    3
}

//      v--- println 2
println(a);

:如果真的是这样,我为什么不直接打电话给run?例如:

val a = run{2};

println(a);//println 2

但是上面的代码在java中可以正常工作,例如:

int a;
try {
    a = 2;
} catch (Throwable ex) {
    a = 3;
}

System.out.println(a); // println 2

【问题讨论】:

    标签: variables lambda functional-programming kotlin


    【解决方案1】:

    问:但是为什么下面的代码也不能工作?

    因为代码可以改变。在定义 lambda 时,变量未初始化,因此如果代码更改并且之后直接调用 lambda,它将无效。 kotlin 编译器希望确保在初始化之前绝对无法访问未初始化的变量,即使通过代理也是如此。

    问:为什么 Kotlin 内联函数无法正常工作,即使我像在 java 中一样在 catch-block 中初始化变量?

    因为run 并不特殊,编译器无法知道主体何时执行。如果你考虑到run 没有被执行的可能性,那么编译器就不能保证变量会被初始化。

    在更改后的示例中,它使用 try-catch 表达式实质上执行 a = run { 2 },这与 run { a = 2 } 不同,因为返回类型保证了结果。

    问:如果真的是这样,那我为什么不直接调用run呢?

    基本上就是这样。关于最终的 Java 代码,事实是 Java 并没有遵循与 Kotlin 完全相同的规则,并且反过来也是如此。仅仅因为某些东西在 Java 中是可能的,并不意味着它就是有效的 Kotlin。

    【讨论】:

    • 感谢您的回答,先生。你的答案和我一样,但为什么我错了?
    • 在调用 lambda 之前,编译器似乎会知道 'a' 是未使用的。错误的更好地方是调用 lambda(或传递给某个函数)的地方。我知道关闭可能是个问题。
    • @Les 嗨。实际上,编译器知道变量a 是否已初始化,因为编译器必须确保所有内容都已初始化以支持 Kotlin 中的空安全性。它必须在编译时从上到下分析代码。但是为什么代码run{a=2},我看不懂。确实,在我的第二个问题中没有任何catch-block 应该可以正常工作。
    • @kiskae - run 函数并不特殊,它将运行块并返回,之后 a 将被初始化。这是一个内联函数,否则我同意编译器必须弄清楚该函数是否实际调用了 lambda。
    【解决方案2】:

    您可以使用以下方法使变量变得惰性...

    val a: Int by lazy { 3 }
    

    显然,您可以使用一个函数来代替 3。但这允许编译器继续运行并保证 a 在使用前被初始化。

    编辑

    虽然问题似乎是“为什么不能完成”。我处于相同的思维框架中,我不明白为什么不(在合理范围内)。我认为编译器有足够的信息来确定 lambda 声明不是对任何闭包变量的引用。因此,我认为当使用 lambda 并且它引用的变量尚未初始化时,它可能会显示不同的错误。

    也就是说,如果编译器作者不同意我的评估(或花费太长时间来解决该功能),我会采取以下措施。

    以下示例显示了一种执行惰性局部变量初始化的方法(适用于 1.1 及更高版本)

    import kotlin.reflect.*
    
    //...
    var a:Int by object {
        private var backing : Int? = null
        operator fun getValue(thisRef: Any?, property: KProperty<*>): Int =
            backing ?: throw Exception("variable has not been initialized") 
        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
            backing = value
        }
    }
    var lambda = { a }
    
    // ...
    a = 3
    println("a = ${lambda()}")
    

    我使用了一个匿名对象来展示正在发生的事情(因为lazy 导致了编译器错误)。该对象可以转换为类似lazy的函数。

    现在,如果程序员忘记在变量被引用之前对其进行初始化,我们可能会回到运行时异常。但 Kotlin 确实尝试过至少帮助我们避免这种情况。

    【讨论】:

    • :),嗨,这不是我想要的。你可以在这里看到我与@hotkey 进一步交谈,但我糟糕的英语会让你感到困惑。如果你能解决我的困惑,我会给予额外的赏金。 stackoverflow.com/questions/45238746/…
    • @holi-java,我明白了。但是我添加了答案,因为像我这样对延迟初始化有疑问的人可能会来到这个页面。
    • 很抱歉让您感到困惑。但我会在某个地方感谢你。
    • 是的,上面的例子使用lazy,但是编译器使用*Ref。但是您的代码可以工作,使用*Ref 生成的字节码不能。这就是为什么我感到困惑。很好,但仍然没有回答我的问题,因为我们的问题是一样的,:)
    猜你喜欢
    • 2011-11-01
    • 2019-06-28
    • 2016-09-15
    • 2011-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-16
    相关资源
    最近更新 更多