为什么尝试打印未初始化的变量并不总是导致错误消息

时间:2015-11-30 09:35:03

标签: java initialization final

有些人可能会发现它类似于SO问题Will Java Final variables have default values?,但该答案并未完全解决这个问题,因为该问题并未在实例初始化程序块中直接打印x的值。

当我尝试在实例初始化程序块中直接打印x时,问题出现了,同时在块结束之前为x分配了一个值:

<案例1
class HelloWorld {

    final int x;

    {
        System.out.println(x);
        x = 7;
        System.out.println(x);    
    }

    HelloWorld() {
        System.out.println("hi");
    }

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

这给出了编译时错误,指出变量x可能尚未初始化。

$ javac HelloWorld.java
HelloWorld.java:6: error: variable x might not have been initialized
        System.out.println(x);
                           ^
1 error
<案例2

我没有直接打印,而是调用一个打印函数:

class HelloWorld {

    final int x;

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

    HelloWorld() {
        System.out.println("hi");
    }

    void printX() {
        System.out.println(x);
    }

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

这正确编译并提供输出

0
7
hi

两种情况之间的概念差异是什么?

6 个答案:

答案 0 :(得分:32)

在JLS §8.3.3. Forward References During Field Initialization中,它表示在以下情况下出现编译时错误:

  

使用在使用后以声明方式显示声明的实例变量有时会受到限制,即使这些实例变量也是如此   在范围内。具体来说,如果所有的话都是编译时错误   以下是真实的:

     
      
  • 在使用实例变量后,类或接口C中的实例变量声明以文本形式出现;

  •   
  • 在C的实例变量初始值设定项或C的实例初始值设定项中,use是一个简单的名称;

  •   
  • 使用不在作业的左侧;

  •   
  • C是封闭使用的最里面的类或接口。

  •   

以下规则附带一些示例,其中最接近您的是:

self.close()

通过方法访问[静态或实例变量]不会以这种方式检查,因此上面的代码会生成输出class Z { static int peek() { return j; } static int i = peek(); static int j = 1; } class Test { public static void main(String[] args) { System.out.println(Z.i); } } ,因为0的变量初始化程序使用类方法ipeek()由其变量初始化程序初始化之前访问变量j的值,此时它仍然具有其默认值(§4.12.5 Initial Values of Variables

因此,总而言之,您的第二个示例编译并执行正常,因为编译器在您调用j并且x实际需要时,不会检查printX()变量是否已初始化放置在运行时,printX()变量将被赋予其默认值(x)。

答案 1 :(得分:12)

阅读JLS,答案似乎在section 16.2.2

  

空白final成员字段V在{{1}范围内的任何方法的主体之前的块(第14.2节)之前是明确分配的(并且绝对不是未分配的)在声明V范围内声明的任何类之前。

这意味着当调用方法时,最终字段在调用之前会被赋值为默认值0,因此当您在方法中引用它时,它会成功编译并输出值0.

但是,当您访问方法之外的字段时,它被视为未分配,因此编译错误。以下代码也将无法编译:

V

,因为:

  
      
  • public class Main { final int x; { method(); System.out.println(x); x = 7; } void method() { } public static void main(String[] args) { } } 在块的任何其他语句V之前被[un]分配iff S在块中紧接在V之前的语句之后被[{1}}分配。
  •   

由于最终字段S在方法调用之前未被分配,因此在它之后仍未分配。

JLS中的这个注释也是相关的:

  

请注意,没有任何规则可以让我们得出结论x在作为V中声明的任何构造函数,方法,实例初始值设定项或静态初始化程序的主体的块之前肯定是未分配的。 。我们可以非正式地得出结论:在{C}中声明的任何构造函数,方法,实例初始化程序或静态初始化程序的主体之前,C并不是绝对未分配的,但是不需要明确说明这样的规则。

答案 2 :(得分:4)

不同之处在于,在第一种情况下,您从初始化程序块调用System.out.println,因此在构造函数之前调用该块。在第一行

System.out.println(x);

变量x尚未初始化,因此您会收到编译错误。

但是在第二种情况下,你调用的实例方法不知道变量是否已经初始化,因此你没有编译错误,你可以看到x的默认值

答案 3 :(得分:4)

好的,这是我的2美分。

我们都知道,只有在构造函数中声明或稍后才能初始化最终变量。记住这个事实,让我们看看到目前为止发生了什么。

没有错误案例:

  

因此,当您在方法内部使用时,它已经有了一个值。

 1) If you initialize it, that value.
 2) If not, the default value of data type. 

错误案例:

  

在初始化块中执行此操作时,您会看到错误。

如果你看docs of initialization block

{
    // whatever code is needed for initialization goes here
}

  

Java编译器将初始化程序块复制到每个构造函数中。因此,此方法可用于在多个构造函数之间共享代码块。

在编译器眼中,你的代码实际上等于

class HelloWorld {

    final int x;
    HelloWorld() {
        System.out.println(x);  ------------ ERROR here obviously
        x = 7;
        System.out.println(x);  
        System.out.println("hi");
    }

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

您在使用它之前就已经开始使用它了。

答案 4 :(得分:1)

案例1:

给你一个编译错误,

因为System.out.println(x);

  

您正在尝试打印从未初始化的x。

案例2:

可以正常工作,因为你没有直接使用任何文字值,而是调用了一些正确的方法。

一般规则是,

  

如果您尝试访问任何从未初始化的变量   然后它会给出编译错误。

答案 5 :(得分:0)

我们在这里处理初始化块。 Java编译器将初始化程序块复制到每个构造函数中。

第二个例子中没有出现编译错误,因为打印x在另一个Frame中,请参阅规范。