为什么不能在 catch 块中初始化最终变量?

Kes*_*uwa 0 java

案例研究1

    final String someString = "Hello World";
    try {
      someString = "Hello";
    } catch (RuntimeException e) {
      someString = "World";
    }
Run Code Online (Sandbox Code Playgroud)

如果我们编译这段代码,我们会得到两个错误:

无法为最终变量 'someString' 赋值 // 第 3 行

无法为最终变量 'someString' 赋值 // 第 5 行

这个,我明白了。它是一个最终变量,您已经为其分配了引用,并且无法再更改它。好的。

案例研究2

    final String someString;
    try {
      someString = "Hello";
    } catch (RuntimeException e) {
      someString = "World";
    }
Run Code Online (Sandbox Code Playgroud)

如果我们编译这段代码,我们只会得到一个错误:

变量“someString”可能已被分配给 // 第 5 行

这个,我不明白。我们可以在 try 块中进行初始化。尽管如此,如果出现问题,即使在实际初始化完成之前,我们也不允许在 catch 块中初始化最终变量。

为什么会这样?我期望我们可以初始化 catch 块中的变量,因为该try块无论如何都会失败。

Joa*_*uer 6

tl;dr:当我们到达 catch 块时,try 块已经失败,但我们不知道它在哪里失败。它可能在分配给变量之前、期间或之后失败。因此,编译器不允许对final变量进行可能的重新分配。

长答案:变量final必须被赋值一次才能被读取。不能少(即从未分配),也不能多(即分配两次)。就一次。

因此,下面的代码是错误的:

final String someString;
someString = "Hello";
someString = "World";
Run Code Online (Sandbox Code Playgroud)

第二个分配将导致编译时错误,因为someString肯定已经分配了。

现在让我们看一下这段代码:

final String someString;
try {
  callSomeMethod();
  someString = "Hello";
  callSomeOtherMethod();
} catch (Exception e) {
  someString = "World";
}
Run Code Online (Sandbox Code Playgroud)

现在我们遇到了一个问题:编译器无法知道导致 catch 块执行的异常何时被抛出:它可能被抛出 incallSomeMethod()或 in callSomeOtherMethod()(或者通过一些神奇的内部问题,即使在尝试分配持续的)。

因此编译器根本不知道someStringwill 是否已经被分配给 catch 块内部(理论上这两个选项在运行时都是可能的!)。如果变量已被赋值,则该赋值必定是编译时错误。如果不是的话就好了。但由于静态分析无法告诉您它是哪一个,因此编译器必须将整个赋值标记为错误。

现在,理论上在某些极端情况下,即使发生异常,编译器理论上也可以知道块中明确分配了变量。try具体来说,如果是第一个动作,则可以证明赋值动作不会抛出异常。但如果是这种情况,那么程序员只需将该赋值移出try 块即可避免整个问题。这可能就是 JLS 中不存在此类规则的原因。