Java最终variables是否有默认值?
我有一个这样的程序:
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".
最终的variables是否有默认值?
如果我这样更改我的代码,
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
任何人都可以请解释这种行为..
http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html ,“初始化实例成员”一章:
Java编译器将初始化块复制到每个构造函数中。
也就是说:
{ printX(); } Test() { System.out.println("const called"); }
performance如下:
Test() { printX(); System.out.println("const called"); }
正如你所看到的,一旦创build了一个实例,最终的字段还没有明确的分配 ,而(来自http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html #jls-8.3.1.2 ):
一个空的final实例variables必须明确的分配在声明的类的每个构造函数的末尾; 否则会发生编译时错误。
虽然它似乎没有在文档中明确指出(至less我还没有find它),但最终的字段必须在构造函数结束之前临时采用其默认值,以便在您具有可预测的值时在分配之前阅读它。
默认值: http : //docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5
在你的第二个片段中,x是在实例创build时初始化的,所以编译器不会抱怨:
Test() { printX(); x = 7; printX(); System.out.println("const called"); }
另请注意,以下方法不起作用。 使用finalvariables的默认值只能通过一个方法来实现。
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"); }
JLS是说 ,你必须在构造函数中(或者在初始化块中 ,这是非常相同的)分配默认值为空白的最终实例variables。 这就是为什么你在第一种情况下得到错误。 但是,它并没有说你不能在构造函数中访问它。 看起来有点奇怪,但你可以在赋值之前访问它,并看到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
在构造函数的开始处明确赋值 ,因为它包含了它在结尾的赋值。
如果你不初始化x
你会得到一个编译时错误,因为x
从来没有初始化。
将x
声明为final意味着它只能在构造函数或初始化块中初始化 (因为这个块将被编译器复制到每个构造函数中)。
在variables初始化之前打印出来的原因是由于手册中定义的行为(参见:“默认值”部分):
默认值
当声明一个字段时,并不总是需要赋值。 已声明但未初始化的字段将被编译器设置为合理的默认值。 一般来说,根据数据types,这个默认值将为零或为空。 然而依靠这样的默认值,通常被认为是糟糕的编程风格。
以下图表总结了上述数据types的默认值。
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
第一个错误是编译器抱怨你有最后一个字段,但没有代码来初始化它 – 很简单。
在第二个示例中,您有代码为其分配一个值,但执行的顺序意味着您在分配前后引用该字段。
任何字段的预分配值都是默认值。
类的所有非final字段初始化为默认值(对于nummeric数据types为0
,对于布尔null
false
,对于引用types为null
,有时称为复杂对象)。 这些字段在构造函数(或实例初始化块)执行之前进行初始化,而不pipe字段是否在构造函数之前或之后声明。
类的最后一个字段没有默认值,并且在类构造函数完成工作之前必须显式初始化一次。
执行块(例如,方法)内部的局部variables没有默认值。 这些字段必须在第一次使用之前被明确地初始化,并且局部variables是否被标记为final是无关紧要的。
让我用最简单的话来说吧。
final
variables需要被初始化,这是语言规范所要求的。 话虽如此,请注意,在申报时没有必要进行初始化。
在对象被初始化之前,需要初始化它。
我们可以使用初始化块来初始化最终的variables。 现在,初始化块有两种types的static
和non-static
你使用的块是一个非静态的初始化块。 所以,当你创build一个对象的时候,Runtime会调用构造函数,然后调用父类的构造函数。
之后,它会调用所有的初始化器(在你的情况下是非静态的初始化器)。
在你的问题中, 情况1 :即使在初始化块完成之后,最终variables仍然未初始化,这是编译器将检测到的错误。
在情况2中 :初始化器将初始化最终variables,因此编译器知道在对象被初始化之前,最终已经被初始化。 因此,它不会抱怨。
现在的问题是,为什么x
取零。 这里的原因是编译器已经知道没有错误,所以在调用init方法时,所有的final将被初始化为默认值,并且一个标志被设置为可以在类似x=7
实际赋值语句中改变。 请参阅下面的init调用:
据我所知,编译器将始终将类variables初始化为默认值(甚至最终variables)。 例如,如果你要初始化一个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
如果我尝试执行它,我得到编译器错误为:variablesx可能没有被初始化的基于java的默认值,我应该得到下面的输出吗?
“这里x是0”。
不,你没有看到输出,因为你首先得到编译时错误。 最终variables确实得到了一个默认值,但是Java语言规范(JLS)要求你在构造函数的最后(LE:我在这里包括初始化块)初始化它们,否则你会得到一个编译时错误将阻止你的代码被编译和执行。
你的第二个例子尊重这个要求,这就是为什么(1)你的代码编译和(2)你得到预期的行为。
今后试着熟悉JLS。 关于Java语言没有更好的信息来源。