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

有些人可能会发现它类似于SO问题Java最终variables是否有默认值? 但是这个答案并没有完全解决这个问题,因为这个问题并不直接在实例初始化块中打印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(); } } 

这给编译时错误,说明variablesx可能没有被初始化。

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

案例2

而不是直接打印,我打电话打印function:

 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 

这两种情况之间的概念区别是什么?

在JLS中,第8.3.3节。 转发引用在字段初始化过程中 ,它指出在下列情况下会出现编译时错误:

尽pipe这些实例variables在范围之内,但是在使用之后使用声明在文本上出现的实例variables有时会受到限制。 特别是,如果以下所有情况都是正确的,则是编译时错误:

  • 在实例variables的使用之后,类或接口C中的实例variables的声明以文本forms出现;

  • 在C的实例variables初始值设定项或C的实例初始值设定项中,用法是简单的名称;

  • 使用不在作业的左侧;

  • C是封闭使用的最内层的类或接口。

下面的规则有几个例子,其中最接近你的是这个例子:

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

通过方法访问[静态variables或实例variables] 不以这种方式检查 ,因此上面的代码产生输出0 ,因为variables初始值设定项为i使用类方法peek()j之前访问variablesj的值由其variables初始值设定项初始化,此时它仍然有其默认值( §4.12.5variables的初始值 )。

所以,总结一下,你的第二个例子编译并且执行得很好,因为编译器在你调用printX()时并没有检查xvariables是否已经被初始化,并且在运行时实际发生了printX()时, xvariables将被赋值其默认值( 0 )。

阅读JLS,答案似乎在16.2.2节 :

在作为V范围内的任何方法主体的块(第14.2节)之前,以及在V范围内声明的任何类的声明之前,空白final成员字段V是明确赋值的(而且也不是明确地未赋值的)。

这意味着当一个方法被调用时,最后一个字段在调用它之前被分配给它的默认值0,所以当你在方法中引用它时,它会成功编译并打印值0。

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

 public class Main { final int x; { method(); System.out.println(x); x = 7; } void method() { } public static void main(String[] args) { } } 

因为:

  • 在块的任何其他语句S之前,如果V在块之前的S之前的语句之后被赋值[un],则V被赋值[un]。

由于最后一个字段x在方法调用之前未被分配,所以在它之后它仍然是未分配的。

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

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

好的,这是我的2美分。

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

没有错误案例:

所以当你在一个方法中使用时,它已经有了一个值。

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

错误情况:

当你在初始化块中这样做时,你会发现错误。

如果你看看docs of initialization blockdocs 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(); } } 

您甚至在初始化之前使用它。

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

 System.out.println(x); 

variablesx还没有初始化,所以你得到编译错误。

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

情况1 :

给你一个编译错误,

因为在System.out.println(x);

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

案例2:

因为你没有直接使用任何文字值,所以你可以调用一些方法,这是正确的。

一般规则是,

如果你正试图访问任何从未初始化的variables,那么它将会出现编译错误。

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

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