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

Mar*_*nik 56 java final language-design try-catch

我相信这里

final int i;
try { i = calculateIndex(); }
catch (Exception e) { i = 1; }
Run Code Online (Sandbox Code Playgroud)

i如果控制到达catch-block,则可能无法分配.但是,Java编译器不同意并声称the final local variable i may already have been assigned.

我还缺少一些细微之处,或者这只是Java语言规范用于识别潜在重新分配的模型的弱点?我的主要担心是这样的事情Thread.stop(),这可能会导致异常被"凭空捏造"抛出,但我仍然看不到它如何在赋值后抛出,这显然是try-block中的最后一个动作.

如果允许,上面的成语将使我的许多方法更简单.请注意,此用例具有一流的语言支持,例如Scala,它始终使用Maybe monad:

final int i = calculateIndex().getOrElse(1);
Run Code Online (Sandbox Code Playgroud)

我认为这个用例是一个非常好的动机,允许一个特殊情况 肯定i是在catch块中未分配.

UPDATE

经过一番思考后,我更加确定这只是JLS模型的一个弱点:如果我在所提出的例子中声明了公理,i当控制到达catch-block时肯定是未分配的",它将不会与任何其他公理冲突或定理.编译器i在catch块中分配之前不允许任何读取,因此i无法查看是否已分配的事实.

dje*_*lin 35

JLS狩猎:

如果分配了最终变量,则为编译时错误,除非在分配之前它是明确未分配的(第16段).

第16章:

如果满足以下所有条件,V肯定会在catch块之前取消分配:

在try块之后,V绝对是未分配的.
在每个属于try块的return语句之前,V绝对是未分配的.
对于属于try块的表单throw e的每个语句中的e后,V肯定是未分配的.
在try块中发生的每个assert语句之后,V肯定是未分配的.
在每个属于try块的break语句之前,V绝对是未分配的,并且其break目标包含(或者是)try语句.
在每个继续语句属于try块并且其continue目标包含try语句之前,V肯定是未分配的.

大胆是我的.在try块之后,不清楚是否i已分配.

此外,在示例中

final int i;
try {
    i = foo();
    bar();
}
catch(Exception e) { // e might come from bar
    i = 1;
}
Run Code Online (Sandbox Code Playgroud)

粗体文本是阻止实际错误分配是非法的唯一条件i=1.因此,这足以证明需要"绝对未分配"的更好条件才能允许原始帖子中的代码.

如果修改了规范以替换此条件

如果catch块捕获未经检查的异常,则在try块之后肯定是未分配的.
如果catch块捕获未经检查的异常,则在最后一个能够抛出catch块捕获的类型的异常的语句之前,V肯定是未分配的.

然后我相信你的代码是合法的.(根据我的特别分析.)

我提交了一个JSR,我希望被忽略,但我很想知道如何处理这些.技术上传真号码是必填字段,如果我在那里输入+ 1-000-000-000 ,我希望它不会造成太大的伤害.


use*_*075 18

遗憾的是,我认为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
Run Code Online (Sandbox Code Playgroud)

所以,虽然你不能轻易编写代码来测试这个,因为它不会编译,方法的调用,存储的值,以及catch之后的skip是三个独立的操作.您可以(但不太可能)发生异常(Thread.interrupt()似乎是步骤4和步骤5之间的最佳示例).这将导致在设置i 进入catch块.

我不知道,你可以故意做一吨的线程和中断这种情况发生(和编译器不会让你写的代码反正),但它因此理论上有可能,我可以进行设置,你可以进入即使使用这个简单的代码,异常处理块也是如此.


Kev*_*lia 7

不太干净(我怀疑你已经在做什么).但这只增加了1行.

final int i;
int temp;
try { temp = calculateIndex(); }
catch (IOException e) { temp = 1; }
i = temp;
Run Code Online (Sandbox Code Playgroud)


Mar*_*nik 4

这是支持这一论点的最有力论据的总结,即当前的明确赋值规则不能在不破坏一致性的情况下放宽(A),其次是我的反论点(B):

  • :在字节码级别上,对变量的写入不是 try 块中的最后一条指令:例如,最后一条指令通常是跳过goto异常处理代码;

  • B:但是如果规则规定在 catch 块内肯定未分配i,则可能无法观察到它的值。不可观察的价值与没有价值一样好;

  • :即使编译器声明i绝对未赋值,调试工具仍然可以看到该值;

  • B:事实上,调试工具总是可以访问未初始化的局部变量,该变量在典型的实现中具有任意值。未初始化的变量和在实际写入发生后初始化突然完成的变量之间没有本质区别。无论此处考虑的特殊情况如何,该工具都必须始终使用附加元数据来了解每个局部变量明确分配该变量的指令范围,并且仅允许在执行发现自身位于该范围内时观察其值。

定论:

该规范可以一致地接收更细粒度的规则,这将允许我发布的示例进行编译。