Java Final变量是否具有默认值?


81

我有一个这样的程序:

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();
    }

}

如果我尝试执行它,我将得到编译器错误:variable x might not have been initialized基于Java默认值,我应该得到以下输出?

"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

谁能解释这个问题。

Answers:


62

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");
}

如您所见,一旦创建了实例,就不会明确分配final字段,而(来自http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html #jls-8.3.1.2):

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

尽管它似乎在文档中没有明确说明(至少我找不到),但final字段必须在构造函数结尾之前临时采用其默认值,以便具有可预测的值,如果你在分配之前先阅读它。

默认值: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");
}

1
可能值得注意的是,在您的示例之一中,对super()的隐式(或显式)调用位于何处。
帕特里克

2
这不能回答为什么不初始化final字段会导致编译错误的原因。
justhalf 2014年

@ sp00m很好的参考-我会将其放在银行中。
波希米亚

2
@justhalf答案缺少关键点。您可以(通过方法)访问处于默认状态的final,但是如果您在构造过程结束之前不进行初始化,则编译器会抱怨。这就是为什么第二次尝试有效(实际上是初始化x),而不是第一次尝试的原因。如果您尝试直接访问空白的final,则编译器也会抱怨。

28

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明确赋值在构造函数的开头,因为它包含在它的最终分配。


实际上,确实说您不能在赋值之前访问它:“每个局部变量(第14.4节)和每个空白的最终字段(第4.12.4节,第8.3.1.2节)在对其值进行任何访问时都必须具有明确分配的值“
l4mpi 2014年

1
它应该是“绝对分配的”,但是这条规则在构造函数方面有奇怪的行为,我已经更新了答案
udalmik

如果有一种方法可以根据某些复杂的条件读取或不读取某个final字段,并且该代码可以在写入字段之前和之后运行,那么在一般情况下,编译器将无法执行知道在写入之前是否实际上会读取该字段。
2014年

7

如果您不初始化x,则会得到一个编译时错误,因为x它从未被初始化。

声明x为final意味着只能在构造函数中或在initializer-block初始化它(因为该块将由编译器复制到每个构造函数中)。

0在变量初始化之前被打印出来的原因是由于手册中定义的行为(请参阅“默认值”部分):

默认值

声明字段时,不一定总是需要分配值。编译器会将已声明但未初始化的字段设置为合理的默认值。一般来说,此默认值将为零或null,具体取决于数据类型。但是,通常认为依赖于此类默认值是不好的编程风格。

下表总结了上述数据类型的默认值。

Data Type   Default Value (for fields)
--------------------------------------
byte        0
short       0
int         0
long        0L
float       0.0f
double      0.0d
char        '\u0000'
String (or any object)      null
boolean     false

4

第一个错误是编译器抱怨您有一个final字段,但是没有用于初始化它的代码-非常简单。

在第二个示例中,您有代码给它分配一个值,但是执行顺序意味着您在分配它之前和之后都引用了该字段。

任何字段的预分配值都是默认值。


2

类的所有非最终字段都初始化为默认值(0对于数字数据类型,false布尔值和null引用类型,有时称为复杂对象)。这些字段在构造函数(或实例初始化块)执行之前进行初始化,而与是否在构造函数之前或之后声明了这些字段无关。

类的final字段没有默认值,并且必须在类构造函数完成其工作之前进行一次显式初始化。

执行块(例如,方法)内部的局部变量没有默认值。这些字段必须在首次使用之前进行显式初始化,并且本地变量是否标记为final都无关紧要。


1

让我用最简单的话讲。

final变量需要初始化,这是语言规范规定的。话虽如此,请注意,在声明时不必初始化它。

在初始化对象之前,需要对其进行初始化。

我们可以使用初始化块来初始化最终变量。现在,初始化块有两种类型 staticnon-static

您使用的块是非静态初始化器块。因此,在创建对象时,运行时将调用构造函数,而后者又将调用父类的构造函数。

之后,它将调用所有初始化程序(在您的情况下为非静态初始化程序)。

在您的问题中,情况1:即使在初始化程序块完成之后,最终变量仍未初始化,这是编译器将检测到的错误。

情况2中:初始化程序将初始化final变量,因此编译器知道在对象初始化之前,final已经被初始化。因此,它不会抱怨。

现在的问题是,为什么x取零。这是因为编译器已经知道没有错误,因此在调用init方法时,所有的finals将被初始化为默认值,并且设置了一个标记,使其可以在类似于的实际赋值语句中进行更改x=7。请参阅下面的初始化调用:

在此处输入图片说明


1

据我所知,编译器将始终将类变量初始化为默认值(甚至最终变量)。例如,如果要初始化一个自身的int值,则该int值将设置为默认值0。请参见下文:

class Test {
    final int x;

    {
        printX();
        x = this.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();
    }
}

上面将打印以下内容:

Here x is 0
Here x is 0
const called

1
最终变量x在OP的代码中不是静态的。
JamesB 2014年

我可以很容易地修改OP的代码以将其初始化为this.x,并且会发生同样的事情。静态与否无关紧要。
Michael D.

我建议您在此处删除静态内容,因为您似乎尚未阅读OP的问题。
JamesB 2014年

如果我以OP的代码为基准,是否有帮助?正如我所说,变量是否为静态都没有关系。我的观点是,将变量初始化为其自身并获取默认值意味着该变量在显式初始化之前确实进行了隐式初始化。
Michael D.

1
它不会编译,因为你试图访问(直接)前进行最后变量的初始化,第6行
卢卡

1

如果我尝试执行它,我将得到编译器错误,因为:可能尚未基于Java默认值初始化变量x,我应该正确获得以下输出?

“这里x是0”。

不会。您没有看到该输出,因为您首先遇到了编译时错误。最终变量确实获得了默认值,但是Java语言规范(JLS)要求您在构造函数的末尾对其进行初始化(LE:我在此处包括初始化块),否则会出现编译时错误,将阻止您的代码被编译和执行。

您的第二个示例尊重要求,这就是为什么(1)您的代码可以编译,以及(2)您获得预期的行为的原因。

将来尝试使自己熟悉JLS。没有关于Java语言的更好的信息来源。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.