【问题标题】:Java uninitialized variable with finally curiosity最终好奇的Java未初始化变量
【发布时间】:2009-08-08 05:57:55
【问题描述】:

当我遇到一段有趣的代码时,我试图为我正在帮助的替代开源 JVM (Avian) 提出晦涩难懂的测试用例,但我很惊讶它没有编译:

public class Test {
    public static int test1() {
        int a;
        try {
            a = 1;
            return a; // this is fine
        } finally {
            return a; // uninitialized value error here
        }
    }
    public static void main(String[] args) {
        int a = test1();
    }
}

最明显的代码路径(我看到的唯一一个)是执行a = 1,“尝试”返回a(第一次),然后执行finally,实际上返回一种。然而,javac 抱怨“a”可能没有被初始化:

Test.java:8:变量 a 可能尚未初始化 返回一个; ^

我能想到的唯一可能导致/允许不同代码路径的事情是,如果在尝试开始之后但在将值 1 分配给 a 之前发生模糊的运行时异常 - 类似于 OutOfMemoryError 或StackOverflowException,但我想不出在代码中的这个地方可能会发生这些情况。

任何更熟悉 Java 标准细节的人能对此有所了解吗?这只是编译器保守的一种情况——因此拒绝编译原本有效的代码——还是这里发生了一些奇怪的事情?

【问题讨论】:

    标签: java variables finally initialization


    【解决方案1】:

    a=1 行可能会发生异常,这似乎违反直觉,但可能会发生 JVM 错误。因此,使变量未初始化。因此,编译器错误是完全有道理的。这就是您提到的模糊 运行时错误。但是,我认为 OutOfMemoryError 远非晦涩难懂,至少应该由开发人员考虑。此外,请记住,设置 OutOfMemoryError 的状态可能发生在另一个线程中,并且将使用的堆内存量推到超过限制的一个操作是变量 a 的赋值。

    无论如何,既然您正在研究编译器设计,我还假设您已经知道在 finally 块中返回值是多么愚蠢。

    【讨论】:

    • 我正在尽我所能在 Avian 中练习 JSR (Jump SubRoutine) jitting 代码(为此,我不得不使用 ECJ,因为 sun javac 不再生成 JSR),而我的测试用例浓缩这个例子要复杂得多。我很清楚这样的代码毫无意义——但如果它可以编译,Avian 必须正确运行它。
    【解决方案2】:

    Java 语言规范要求在使用变量之前对其进行赋值。 JLS 定义了称为“明确分配”规则的特定规则。所有 Java 编译器都需要遵守它们。

    JLS 16.2.15:

    V 肯定在 finally 块之前分配,当且仅当 V 肯定在 try 语句之前分配。

    换句话说,在考虑 finally 语句时,不考虑 try-catch-finally 语句分配中的 try 和 catch 块语句。

    不用说,该规范在这里非常保守,但他们宁愿规范简单但有点受限(相信规则已经很复杂),而不是宽松但难以理解和推理。

    编译器必须遵循这些定义赋值规则,因此所有编译器都会发出相同的错误。除了JLS 指定的抑制任何错误之外,编译器不得执行任何额外的分析。

    【讨论】:

      【解决方案3】:

      我相信这只是由于 try-catch-finally 关系的语义。来自Java Language Specification

      如果执行 try 块 正常完成,然后finally 块被执行...

      如果执行 try 块 由于投掷而突然完成 价值V...

      如果执行 try 块 突然完成任何其他 原因 R,那么 finally 块是 执行...

      最后一个案例似乎与这里最相关。如果 try 块由于任何原因突然完成,finally 块似乎应该能够正确执行。显然,如果 try 块在分配之前结束,finally 块将无效。不过,正如您所说,这不太可能。

      【讨论】:

        【解决方案4】:

        很可能需要 javac 来做出笼统的假设,即在 try 块中的任何时候都可能发生异常,即使在赋值期间也是如此,因此 finally 可能会返回一个未初始化的变量。理论上,它可以做一个详细的分析,发现在通过try块的所有路径中'a'总是会成功初始化,但这是很多工作几乎没有收获。

        现在如果有人能指出 Java 语言规范中的相关部分......

        【讨论】:

        • 此行为由语言规范指定。编译器必须发出该错误,并且不允许使用任何其他分析来抑制它。
        • @msaeed:谢谢,这正是我所怀疑的。
        【解决方案5】:

        如果编译器不确定以下(成功),则会在条件块中发生编译器错误 语句会像这样运行

        int i=5;int d;
        if(i<10)
        {system.out.println(d);}
        

        如果条件语句是确定的,不会发生编译器错误,并且不会像这样到达毫无疑问的代码

        int i;
        

        if(true){}

        else
        {System.out.println(d);}
        

        如果条件语句肯定会出现并且会到达毫无疑问的代码,则会发生编译器错误

        int i;
        if(true)
        {System.out.println(d);}
        else{}
        

        由于 try 块属于此,它们遵循相同的规则。

        【讨论】:

          【解决方案6】:

          我猜 Java 编译器假设了最坏的情况 - 不能保证 try 块中的任何内容由于某种原因甚至被执行。所以它的投诉是有效的。变量可能尚未初始化。

          【讨论】:

          • 从到目前为止我为 Sun SCJP 学习的知识来看,编译器在条件代码块中分配变量时会出现问题。代码在没有 finally 的情况下运行良好,因此它确实假设 try 块会发生。我相信由于所有异常都发生在运行时,它们不是编译器的关注点
          【解决方案7】:

          编译器在这里只是保守。

          【讨论】:

          • 其实这里需要编译器保守一点;请参阅@msaeed 的回答。
          猜你喜欢
          • 1970-01-01
          • 2019-07-12
          • 2011-02-15
          • 1970-01-01
          • 2016-03-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-07-04
          相关资源
          最近更新 更多