首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >即使赋值是try中的最后一个操作,在catch中也可以重新赋值最后一个变量吗?

即使赋值是try中的最后一个操作,在catch中也可以重新赋值最后一个变量吗?
EN

Stack Overflow用户
提问于 2013-06-13 04:47:30
回答 6查看 3.7K关注 0票数 57

我非常确信在这里

代码语言:javascript
复制
final int i;
try { i = calculateIndex(); }
catch (Exception e) { i = 1; }

如果控制到达catch-block,则不可能已经分配了i。然而,Java编译器不同意这一点,并声称是the final local variable i may already have been assigned

这里是否仍然遗漏了一些微妙之处,或者这只是Java语言规范用来识别潜在重新分配的模型的一个弱点?我主要担心的是像Thread.stop()这样的东西,这可能会导致“凭空”抛出一个异常,但我仍然不明白如何在赋值之后抛出它,这显然是try-block中的最后一个操作。

如果允许的话,上面的习惯用法将使我的许多方法变得更简单。请注意,此用例在Scala等语言中具有一流的支持,这些语言始终使用可能的monad:

代码语言:javascript
复制
final int i = calculateIndex().getOrElse(1);

我认为这个用例是一个很好的动机,可以允许在catch-block中i绝对是未赋值的这种特殊情况。

更新

经过一番思考,我更加确定这只是JLS模型的一个弱点:如果我声明这个公理“在这个例子中,当控制到达catch-block时,i绝对是未赋值的”,它不会与任何其他公理或定理冲突。在catch-block中赋值之前,编译器不允许对i进行任何读取,因此无法观察到i是否已被赋值。

EN

回答 6

Stack Overflow用户

发布于 2013-06-21 00:24:43

遗憾的是,我认为JVM是正确的。虽然直观上看代码是正确的,但在查看IL的上下文中是有意义的。我创建了一个简单的run()方法,它主要模拟您的情况(这里有简化的注释):

代码语言:javascript
复制
0: aload_0
1: invokevirtual  #5; // calculateIndex
4: istore_1
5: goto  17
// here's the catch block
17: // is after the catch

因此,虽然您不能轻松地编写代码来测试它,因为它不会编译,但方法的调用、存储值和在catch之后跳到是三个独立的操作。在第4步和第5步之间可能会发生异常(Thread.interrupt()似乎就是最好的例子),这将导致在设置i之后进入catch块。

我不确定您是否可以故意使用大量线程和中断来实现这一点(而且编译器无论如何都不会让您编写这些代码),但是从理论上讲,我可以被设置,并且您可以进入异常处理块,即使使用这段简单的代码也是如此。

票数 18
EN

Stack Overflow用户

发布于 2013-06-13 04:53:27

不是很干净(我怀疑你已经在做什么了)。但这只会额外增加一行。

代码语言:javascript
复制
final int i;
int temp;
try { temp = calculateIndex(); }
catch (IOException e) { temp = 1; }
i = temp;
票数 7
EN

Stack Overflow用户

发布于 2013-06-19 13:33:39

您是正确的,如果赋值是try块中的最后一个操作,我们知道一旦进入catch块,变量就不会被赋值。然而,将“最后一次操作”的概念形式化将大大增加规范的复杂性。考虑一下:

代码语言:javascript
复制
try {
    foo = bar();
    if (foo) {
        i = 4;
    } else {
        i = 7;
    }
}

这个特性会有用吗?我不这么认为,因为最后一个变量必须只赋值一次,而不是最多赋值一次。在您的示例中,如果抛出Error,变量将被取消赋值。您可能并不关心变量是否超出了作用域,但情况并不总是如此(在同一个Error语句中或在同一个try语句中,可能有另一个catch块来捕获try)。例如,考虑一下:

代码语言:javascript
复制
final int i;
try {
    try {
        i = foo();
    } catch (Exception e) {
        bar();
        i = 1;
    }
} catch (Throwable t) {
    i = 0;
}

这是正确的,但如果对bar()的调用是在赋值i之后发生的(例如在finally子句中),或者我们对其close方法抛出异常的资源使用try- with -resources语句,则不会正确。

考虑到这一点,会给规范增加更多的复杂性。

最后,有一个简单的解决办法:

代码语言:javascript
复制
final int i = calculateIndex();

代码语言:javascript
复制
int calculateIndex() {
    try {
        // calculate it
        return calculatedIndex;
    } catch (Exception e) {
        return 0;
    }
}

很明显,我是被指派的。

简而言之,我认为添加此功能会大大增加规范的复杂性,但几乎没有什么好处。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/17075061

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档