我非常确信在这里
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:
final int i = calculateIndex().getOrElse(1);我认为这个用例是一个很好的动机,可以允许在catch-block中i绝对是未赋值的这种特殊情况。
更新
经过一番思考,我更加确定这只是JLS模型的一个弱点:如果我声明这个公理“在这个例子中,当控制到达catch-block时,i绝对是未赋值的”,它不会与任何其他公理或定理冲突。在catch-block中赋值之前,编译器不允许对i进行任何读取,因此无法观察到i是否已被赋值。
发布于 2013-06-21 00:24:43
遗憾的是,我认为JVM是正确的。虽然直观上看代码是正确的,但在查看IL的上下文中是有意义的。我创建了一个简单的run()方法,它主要模拟您的情况(这里有简化的注释):
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块。
我不确定您是否可以故意使用大量线程和中断来实现这一点(而且编译器无论如何都不会让您编写这些代码),但是从理论上讲,我可以被设置,并且您可以进入异常处理块,即使使用这段简单的代码也是如此。
发布于 2013-06-13 04:53:27
不是很干净(我怀疑你已经在做什么了)。但这只会额外增加一行。
final int i;
int temp;
try { temp = calculateIndex(); }
catch (IOException e) { temp = 1; }
i = temp;发布于 2013-06-19 13:33:39
您是正确的,如果赋值是try块中的最后一个操作,我们知道一旦进入catch块,变量就不会被赋值。然而,将“最后一次操作”的概念形式化将大大增加规范的复杂性。考虑一下:
try {
foo = bar();
if (foo) {
i = 4;
} else {
i = 7;
}
}这个特性会有用吗?我不这么认为,因为最后一个变量必须只赋值一次,而不是最多赋值一次。在您的示例中,如果抛出Error,变量将被取消赋值。您可能并不关心变量是否超出了作用域,但情况并不总是如此(在同一个Error语句中或在同一个try语句中,可能有另一个catch块来捕获try)。例如,考虑一下:
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语句,则不会正确。
考虑到这一点,会给规范增加更多的复杂性。
最后,有一个简单的解决办法:
final int i = calculateIndex();和
int calculateIndex() {
try {
// calculate it
return calculatedIndex;
} catch (Exception e) {
return 0;
}
}很明显,我是被指派的。
简而言之,我认为添加此功能会大大增加规范的复杂性,但几乎没有什么好处。
https://stackoverflow.com/questions/17075061
复制相似问题