【问题标题】:Will Java Final variables have default values?Java Final 变量会有默认值吗?
【发布时间】:2014-09-19 08:55:32
【问题描述】:

我有一个这样的程序:

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".

final 变量会有默认值吗?

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

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

谁能解释一下这种行为..

【问题讨论】:

    标签: java final


    【解决方案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");
    }
    

    如您所见,一旦创建了实例,最终字段就不是definitely assigned,而(来自http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.2):

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

    虽然在文档中似乎没有明确说明(至少我还没有找到它),但 final 字段必须在构造函数结束之前临时取其默认值,以便它有一个 @ 987654324@ 如果您在分配之前阅读它。

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

    在您的第二个 sn-p 中,x 在实例创建时被初始化,因此编译器不会抱怨:

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

    另请注意,以下方法不起作用。只能通过方法使用 final 变量的默认值。

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

    【讨论】:

    • 在你的一个例子中,对 super() 的隐式(或显式)调用可能值得注意。
    • 这没有回答为什么不初始化最终字段会导致编译错误。
    • @sp00m 很好的参考资料 - 我会把它存入银行。
    • @justhalf 答案缺少关键点。您可以在默认状态下访问 final(通过方法),但是如果您在构造过程结束之前没有对其进行初始化,编译器会报错。这就是为什么第二次尝试有效(它实际上初始化了 x),但不是第一次。如果你尝试直接访问一个空白的final,编译器也会报错。
    【解决方案2】:

    据我所知,编译器总是会将类变量初始化为默认值(甚至是最终变量)。例如,如果您要将 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
    

    【讨论】:

    • 最终变量 x 在 OP 的代码中不是静态的。
    • 我可以很容易地修改 OP 的代码以初始化为 this.x,同样的事情也会发生。不管是不是静态的。
    • 我建议您删除此处的静态内容,因为您似乎没有阅读 OP 的问题。
    • 如果我以 OP 的代码为基准,是否有帮助?正如我所说,变量是否是静态的并不重要。我的观点是,将变量初始化为自身并获取默认值意味着该变量在被显式初始化之前会被隐式初始化。
    • 它无法编译,因为您在第 6 行初始化之前尝试(直接)访问最终变量。
    【解决方案3】:

    如果您不初始化 x,您将收到编译时错误,因为 x 从未初始化。

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

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

    默认值

    声明字段时并不总是需要赋值。 已声明但未初始化的字段将设置为 编译器的合理默认值。一般来说,这个默认 将为零或空,具体取决于数据类型。依靠这样的 然而,默认值通常被认为是糟糕的编程 风格。

    下图总结了以上数据的默认值 类型。

    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】:

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

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

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

      【讨论】:

        【解决方案5】:

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

        UPD. 正如@I4mpi 所提到的,JLS defines 在任何访问之前都应该明确分配每个值的规则:

        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.

        但是,关于构造函数和字段,它也有一个interesting rule

        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 的值在构造函数的开头肯定赋值,因为它包含在其末尾的赋值。

        【讨论】:

        • 实际上,it does say you can not access it before assignment:“每个局部变量(第 14.4 节)和每个空白最终字段(第 4.12.4 节、第 8.3.1.2 节)在对其值进行任何访问时都必须有一个明确的赋值发生”
        • 它应该是“肯定分配的”,但是这个规则在构造函数方面有奇怪的行为,我已经更新了答案
        • 如果有一种代码方法,根据一些复杂的条件,可能会也可能不会读取final字段,并且如果该代码可以在字段写入之前和之后运行,在一般情况下,编译器无法知道它是否真的会在字段被写入之前读取它。
        【解决方案6】:

        让我用最简单的话来说吧。

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

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

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

        您使用的块是一个非静态初始化块。因此,当您创建一个对象时,Runtime 会调用构造函数,而后者又会调用父类的构造函数。

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

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

        案例2中:初始化器会初始化final变量,因此编译器知道在对象初始化之前,final就已经被初始化了。因此,它不会抱怨。

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

        【讨论】:

          【解决方案7】:

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

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

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

          【讨论】:

            【解决方案8】:

            如果我尝试执行它,我会收到编译器错误:变量 x 可能没有根据 java 默认值初始化,我应该得到以下输出吗??

            “这里 x 是 0”。

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

            您的第二个示例符合要求,这就是为什么 (1) 您的代码可以编译并且 (2) 您会得到预期的行为。

            以后尝试熟悉 JLS。没有比 Java 语言更好的信息来源了。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-03-30
              • 2011-08-27
              • 2019-04-03
              • 2021-03-08
              • 2012-06-25
              • 1970-01-01
              • 1970-01-01
              • 2011-10-14
              相关资源
              最近更新 更多