Java Final 变量是否具有缺省值?

2022-08-31 12:31:35

我有一个这样的程序:

class Test {

    final int x;

    {
        printX();
    }

    Test() {
        System.out.println("const called");
    }

    void printX() {
        System.out.println("Here x is " + x);
    }

    public static void main(String[] args) {
        Test t = new Test();
    }

}

如果我尝试执行它,我得到编译器错误,因为:基于java默认值,我应该得到下面的输出对吗?variable x might not have been initialized

"Here x is 0".

最终变量是否具有 dafault 值?

如果我像这样更改我的代码,

class Test {

    final int x;

    {
        printX();
        x = 7;
        printX();
    }

    Test() {
        System.out.println("const called");
    }

    void printX() {
        System.out.println("Here x is " + x);
    }

    public static void main(String[] args) {
        Test t = new Test();
    }

}

我得到的输出是:

Here x is 0                                                                                      
Here x is 7                                                                                     
const called

任何人都可以解释一下这种行为。


答案 1

http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html,“初始化实例成员”一章:

Java 编译器将初始值设定项块复制到每个构造函数中。

也就是说:

{
    printX();
}

Test() {
    System.out.println("const called");
}

行为与以下行为完全相同:

Test() {
    printX();
    System.out.println("const called");
}

因此,您可以看到,一旦创建了实例,最终字段尚未明确分配,而(从 http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.2):

空白的最终实例变量必须在声明它的类的每个构造函数的末尾明确分配;否则会发生编译时错误。

虽然它似乎在文档中没有明确说明(至少我无法找到它),但最终字段必须在构造函数结束之前临时获取其默认值,以便在其赋值之前阅读它,则它具有可预测的值

默认值:http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5

在第二个代码段上,x 在创建实例时初始化,因此编译器不会抱怨:

Test() {
    printX();
    x = 7;
    printX();
    System.out.println("const called");
}

另请注意,以下方法不起作用。仅允许通过方法使用最终变量的默认值。

Test() {
    System.out.println("Here x is " + x); // Compile time error : variable 'x' might not be initialized
    x = 7;
    System.out.println("Here x is " + x);
    System.out.println("const called");
}

答案 2

JLS是说你必须在构造函数(或初始化块中)将默认值分配给空白的最终实例变量,这非常相似)。这就是为什么您在第一种情况下遇到错误的原因。但是,它并没有说您之前不能在构造函数中访问它。看起来有点奇怪,但您可以在赋值之前访问它,并查看int - 0的默认值。

UPD。正如@I4mpi中提到的,JLS定义了在每个访问之前应明确分配每个值的规则:

Each local variable (§14.4) and every blank final field (§4.12.4, §8.3.1.2) must have a definitely assigned value when any access of its value occurs.

但是,它在构造函数和字段方面也有一个有趣的规则

If C has at least one instance initializer or instance variable initializer then V is [un]assigned after an explicit or implicit superclass constructor invocation if V is [un]assigned after the rightmost instance initializer or instance variable initializer of C.

因此,在第二种情况下,该值肯定是在构造函数的开头赋值的,因为它在其末尾包含赋值。x


推荐