6

当我遇到一段有趣的代码时,我试图为我正在帮助的替代开源 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,它实际上返回a。但是,javac 抱怨“a”可能没有被初始化:

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

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

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

4

7 回答 7

10

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

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

于 2009-08-08T06:54:43.827 回答
7

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

JLS 16.2.15

V 是在 finally 块之前明确分配的,当且仅当 V 是在 try 语句之前明确分配的。

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

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

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

于 2009-08-08T06:58:15.923 回答
3

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

如果 try 块的执行正常完成,则执行 finally 块......

如果 try 块的执行由于抛出 V 值而突然完成...

如果 try 块的执行由于任何其他原因 R 突然完成,则执行 finally 块......

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

于 2009-08-08T06:19:05.817 回答
2

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

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

于 2009-08-08T06:19:20.013 回答
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{}

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

于 2012-11-01T17:09:57.303 回答
1

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

于 2009-08-08T07:24:49.957 回答
0

编译器在这里只是保守。

于 2009-08-08T06:11:18.420 回答