当前位置: 首页 > 知识库问答 >
问题:

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

艾浩广
2023-03-14

我很确信在这里

final int i;
try { i = calculateIndex(); }
catch (Exception e) { i = 1; }

如果控制到达捕获块,不可能已经被分配了。但是,Java编译器不同意并声称可能已经分配了最终的局部变量i

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

如果允许的话,上面的习惯用法会使我的许多方法变得更简单。注意,这个用例在语言中有一流的支持,比如Scala,它始终使用Maybe monad:

final int i = calculateIndex().getOrElse(1);

我认为这个用例提供了一个很好的动机,允许在catch块中明确未分配<code>I

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

共有3个答案

鞠边浩
2023-03-14

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

final int i;
int temp;
try { temp = calculateIndex(); }
catch (IOException e) { temp = 1; }
i = temp;
杜志
2023-03-14

遗憾的是,我认为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块。

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

呼延智明
2023-03-14

JLS狩猎:

如果最终变量被分配给,则是编译时错误,除非它在分配之前肯定未分配(§16)。

第16章引述:

在以下所有条件都成立的情况下,在捕获块之前,V 肯定是未分配的:

在try块之后,v肯定是未赋值的。< br >在属于try块的每个return语句之前,V肯定是未赋值的。< br >在属于try块的form throw e的每个语句中,V在e之后肯定是未赋值的。< br >在try块中出现的每个assert语句后,V肯定没有赋值。< br >在属于try块并且其break目标包含(或者是)try语句的每个break语句之前,V肯定是未赋值的。< br >在属于try块并且其continue目标包含try语句的每个continue语句之前,V肯定是未赋值的。

Bold是我的。在try块之后,不清楚是否分配了i

此外,在该示例中

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

粗体文本是防止实际错误赋值 i=1 成为非法的唯一条件。因此,这足以证明需要更精细的“绝对未分配”条件才能允许在原始帖子中使用代码。

如果对规范进行了修订,则将该条件替换为:


如果catch块捕获未检查的异常,则在try块之后绝对未分配V。
如果catch块捕获未检查的异常,则在能够抛出由catch块捕获的类型的异常的最后一条语句之前,V绝对未分配。

那么我相信你的代码是合法的。(根据我的临时分析。)

我为此提交了一个JSR,我希望它被忽略,但我很好奇这些是如何处理的。从技术上讲,传真号码是必填字段,我希望如果我在那里输入1-000-000-000,它不会造成太大的损害。

 类似资料:
  • 以下代码不能用javac 1.8.0_144和ECJ编译: > 未声明为最终。 在赋值表达式(§15.26)中,它从不作为左手边出现。(请注意,包含 初始值设定项的局部变量声明符不是赋值表达式。) 它从不作为前缀或后缀递增或递减运算符的操作数出现(§15.14,§15.15)。 它从不作为前缀或后缀递增或递减运算符的操作数出现。 方法、构造函数、λ或异常参数(§8.4.1,§8.8.1,§9.4,

  • 因为我相信这是一个很好的编程实践,所以如果我的所有(局部或实例)变量只需要编写一次,我就将它们设为< code>final。 但是,我注意到当变量赋值可以抛出异常时,您不能将所述变量设为最终变量: 有没有办法在不诉诸临时变量的情况下做到这一点?(或者这不是最终修饰符的正确位置?)

  • 问题内容: 我很相信这里 如果控制到达捕获块,则不可能已经分配。但是,Java编译器不同意并主张。 我在这里仍然缺少一些微妙之处,还是这仅仅是Java语言规范用来识别潜在重新分配的模型的弱点?我主要担心的是,可能会导致异常抛出“异常”,但是我仍然看不到分配后如何抛出异常,这显然是try块中的最后一个动作。 如果允许,上述成语会使我的许多方法变得更简单。请注意,该用例在诸如Scala之类的语言中具有

  • 我的代码是这样的: 但最后一句话: 总是停止编译说我需要给新变量分配一个返回值?在if语句之前,已经为k分配了一个值。当我把随机k语句放在if语句中时,它似乎是有效的,但这使得它毫无价值,不是吗?编辑器本身没有错误,但是当我编译时,它给了我这个: 线程“main”java中出现异常。lang.IndexOutOfBoundsException:索引:41,大小:36。util。ArrayList。

  • 问题内容: 如果我有两个线程同时修改结构上的字符串字段,我是否总是会看到分配给该字段的一个或另一个字符串,但没有别的? 问题答案: 否。如果您需要原子操作,则存在。 在转到内存模型将所有相关的细节。在“内存模型”文档的顶部: 修改由多个goroutine同时访问的数据的程序必须序列化此类访问。 要序列化访问,请使用通道操作或其他同步原语(例如和 软件包中的原语)保护数据。