为什么试图打印未初始化的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()
时并没有检查x
variables是否已经被初始化,并且在运行时实际发生了printX()
时, x
variables将被赋值其默认值( 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 block
的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(); } }
您甚至在初始化之前使用它。
不同之处在于,在第一种情况下,您正在从初始化程序块调用System.out.println
,以便在构造函数之前调用该块。 在第一行
System.out.println(x);
variablesx
还没有初始化,所以你得到编译错误。
但在第二种情况下,你调用实例方法,它不知道variables是否已经被初始化,所以你没有编译错误,你可以看到x
的默认值
情况1 :
给你一个编译错误,
因为在System.out.println(x);
您正尝试打印从未初始化的x。
案例2:
因为你没有直接使用任何文字值,所以你可以调用一些方法,这是正确的。
一般规则是,
如果你正试图访问任何从未初始化的variables,那么它将会出现编译错误。
我们在这里处理初始化块。 Java编译器将初始化块复制到每个构造函数中。
第二个例子中没有编译器错误,因为在另一个Frame中打印x,请参考规范。