【问题标题】:Assignment of effectively final variable in try/catch statement在 try/catch 语句中有效地分配最终变量
【发布时间】:2017-12-22 13:31:52
【问题描述】:

以下代码不能用 javac 1.8.0_144 和 ecj 编译:

private LongSupplier foo() {
    long fileSize;
    try {
        fileSize = canThrow();
    } catch (IOException e) {
        fileSize = 42;
    }

    LongSupplier foo = () -> 1 + fileSize;
    return foo;
}

我想知道这是否是编译器中的错误。 The definition of effectively final in the JLS 是:

某些未声明为 final 的变量反而被认为是有效的 final:

  • 如果满足以下所有条件,则声明器具有初始化程序(第 14.4.2 节)的局部变量实际上是最终变量:

    • 它没有被宣布为最终的。

    • 它永远不会出现在赋值表达式的左侧(第 15.26 节)。 (注意局部变量声明器包含
      初始化器不是赋值表达式。)

    • 它永远不会作为前缀或后缀递增或递减运算符的操作数出现(第 15.14 节、第 15.15 节)。

  • 如果满足以下所有条件,则声明符缺少初始化程序的局部变量实际上是最终变量:

    • 它没有被宣布为最终的。

    • 当它出现在赋值表达式的左边时,它肯定是未赋值的,也不是绝对赋值的 分配前;也就是说,它肯定是未分配的,而不是 肯定在赋值右侧之后赋值 表达式(§16(明确赋值))。

    • 它永远不会作为前缀或后缀递增或递减运算符的操作数出现。

  • 出于
    确定它是否有效最终,作为局部变量
    其声明器有一个初始化器。

我的阅读是,在第 2 条中,try/catch 块中的赋值是允许的,因为 fileSize 在赋值之前肯定是未赋值的。

我认为解释拒绝代码的原因是:

  • fileSize 在 try 块之前肯定是未分配的
  • fileSize 是在fileSize = canThrow() 之后赋值的(肯定的?好像16.1.8 不关心赋值中的异常)
  • fileSize 在 try 块之后分配
  • fileSize 在 catch 块之前不是绝对未分配的,因此在 catch 块中分配之前也不是绝对未分配的。
  • 因此,4.12.4 的第 2 条不适用于此处

这是正确的吗?

【问题讨论】:

  • 我会说 fileSize 实际上不是最终的,因为它可以在fileSize = 0 异常或其他情况之后更改。即,如果您将变量设为 final,它将无法编译
  • @PeterLawrey 问题是 javac 接受代码,尽管恕我直言它不应该。定义的任何条款都不适用于此处,因此应该被拒绝,尽管我没有看到技术问题,为什么在 lambda 声明后未分配变量时无法完成。删除初始化后,javac 仍然接受代码(恕我直言,这是正确的),但 ecj 仍然拒绝它。
  • 我同意这种行为是合理的,尽管不符合规范,这意味着它将来可能会中断。
  • 对不起,我编译了错误的文件。 ecj 和 javac 都拒绝该代码,但我不确定这是否符合 JLS。

标签: java lambda language-lawyer final jls


【解决方案1】:

“有效最终”的定义表明添加final 修饰符不应改变任何内容。让我们这样做并得到一个更清晰的错误:

error: variable fileSize might already have been assigned
                    fileSize = 42;
                    ^

所以这与Final variable assignment with try/catch 完全相同(也提供了使用第二个最终变量的解决方法),即变量出现在赋值的左侧,这意味着它不是绝对未赋值。

【讨论】:

    【解决方案2】:

    (为了良好的顺序,try-catch 与问题无关:他们只是争辩说 catch 异常参数被认为是最终的。)

    “实际上是最终的”意图是建立在有两个线程的基础上的,每个线程都有一个同名变量的副本。这两个线程/变量的生命周期是不同的。他们希望防止在一个线程中发生更改,这将需要一些同步和生命周期检查。

    所以他们绝对不想要一个任务。作为语言设计决策。

    确实,canThrow could 中的内部线程使用 fileSize 仍然为 0 catch 将另一个变量 fileSize 设置为 42。我认为您认为引发的异常表示另一个线程死了。

    在这种情况下,您想要的是 Future/FutureTask 之类的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-06
      • 2014-08-09
      • 1970-01-01
      • 1970-01-01
      • 2018-10-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多