Java未初始化的变量,最终带来好奇心

时间:2009-08-08 05:57:56

标签: java variables finally initialization

当我遇到一些有趣的代码时,我试图为我正在帮助的替代开源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,“尝试”返回(第一次),然后执行finally,其中实际返回一个。但是,javac抱怨“a”可能尚未初始化:

    Test.java:8: variable a might not have been initialized  
        return a;  
               ^  

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

任何更熟悉Java标准细节的人都可以对此有所了解吗?这只是编译器保守的情况 - 因此拒绝编译本来是有效代码的东西 - 或者这里有什么陌生的东西?

7 个答案:

答案 0 :(得分:10)

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

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

答案 1 :(得分:7)

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

JLS 16.2.15

  

在finally块之前明确赋值V iff V在try语句之前明确赋值。

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

毋庸置疑,这个规范在这里非常保守,但他们宁愿让规范变得简单而有点受限(相信规则已经很复杂),而不是宽容但难以理解和推理。

编译器必须遵循这些Definite Assignment规则,因此所有编译器都会发出相同的错误。不允许编译器执行任何额外的分析,而不是JLS指定抑制任何错误。

答案 2 :(得分:3)

我认为这只是因为try-catch-finally关系的语义。来自Java Language Specification

  

如果执行try块   正常完成,然后终于完成   阻止执行...

     

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

     

如果执行try块   突然完成其他任何事情   原因R,那么最后的块是   执行...

最后一个案例似乎是最相关的。如果try块由于任何原因突然完成,似乎应该能够正确执行finally块。显然,如果try块在赋值之前结束,则finally块将无效。尽管如你所说,这并不是特别可能。

答案 3 :(得分:2)

javac很可能需要做出假设,即在分配期间,try块中的任何一点都可能发生异常,因此finally可能会返回一个未初始化的变量。从理论上讲,它可以做一个详细的分析,并发现在试块中的所有路径'a'总是会成功初始化,但这几乎没有任何好处。

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

答案 4 :(得分:2)

如果编译器不确定以下(成功),则在条件块中发生

编译器错误 语句将像

一样运行
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{}

由于尝试块属于这个,他们遵循相同的规则。

答案 5 :(得分:1)

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

答案 6 :(得分:0)

编译器在这里保守。