我有一个这样的程序:
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".
最终变量是否具有dafault值?
如果我改变我的代码,
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
任何人都可以解释这种行为..
答案 0 :(得分:60)
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):
必须明确指定空白的最终实例变量 它所在类的每个构造函数的结尾 声明;否则会发生编译时错误。
虽然在文档中似乎没有明确说明(至少我无法找到它),但是最终字段必须在构造函数结束之前临时获取其默认值,以便它具有{ {3}}如果你在分配之前阅读它。
在你的第二个片段中,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");
}
答案 1 :(得分:27)
JLS 是saying,必须将默认值分配给构造函数中的空白最终实例变量(或初始化块这是非常相同的)。这就是你在第一种情况下得到错误的原因。但是它并没有说你之前无法在构造函数中访问它。看起来很奇怪,但您可以在分配之前访问它并查看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
在构造函数的开头是明确赋值,因为它包含最后的赋值。
答案 2 :(得分:7)
如果您没有初始化x
,那么您将收到编译时错误,因为x
永远不会被初始化。
将x
声明为final意味着它只能在构造函数或initializer-block中初始化(因为此块将由编译器复制到每个构造函数中)。
在初始化变量之前打印出0
的原因是由于manual中定义的行为(请参阅:“默认值”部分):
声明字段时并不总是需要指定值。 声明但未初始化的字段将设置为a 编译器合理默认。一般来说,这是默认的 将为零或null,具体取决于数据类型。依靠这样的 但是,默认值通常被认为是错误的编程 风格。
下表总结了上述数据的默认值 类型。
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
答案 3 :(得分:4)
第一个错误是编译器抱怨你有一个final字段,但没有代码来初始化它 - 很简单。
在第二个示例中,您有代码为其分配值,但执行顺序意味着您在分配之前和之后引用该字段。
任何字段的预先指定的值都是默认值。
答案 4 :(得分:2)
类的所有非final字段初始化为默认值(0
表示数字数据类型,false
表示布尔值,null
表示引用类型,有时称为复杂对象。这些字段在构造函数(或实例初始化块)执行之前初始化,而不管是否在构造函数之前或之后声明了字段。
类的最终字段具有无默认值,并且必须在类构造函数完成其作业之前显式初始化一次。
执行块内部的局部变量(例如,方法)没有默认值。这些字段必须在首次使用之前显式初始化,并且局部变量是否标记为最终字段并不重要。
答案 5 :(得分:1)
据我所知,编译器总是将类变量初始化为默认值(甚至是最终变量)。例如,如果要将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
答案 6 :(得分:1)
让我用最简单的话来说明。
需要初始化 final
个变量,这是语言规范要求的。
话虽如此,但请注意,在声明时没有必要对其进行初始化。
需要在初始化对象之前初始化它。
我们可以使用初始化块来初始化最终变量。现在,初始化块有两种类型
static
和non-static
您使用的块是非静态初始化块。因此,当您创建一个对象时,Runtime将调用构造函数,而后者将调用父类的构造函数。
之后,它将调用所有初始值设定项(在您的情况下是非静态初始值设定项)。
在您的问题中,案例1 :即使在初始化程序块完成后,最终变量仍然未初始化,这是编译器将检测到的错误。
在 case 2 中:初始化器将初始化最终变量,因此编译器知道在初始化对象之前,final已经初始化。因此,它不会抱怨。
现在问题是,为什么x
取零。这里的原因是编译器已经知道没有错误,因此在调用init方法时,所有的决定都将初始化为默认值,并设置一个标志,它们可以在类似于x=7
的实际赋值语句中更改。
请参阅下面的init调用:
答案 7 :(得分:1)
如果我尝试执行它,我收到编译器错误:变量x可能没有基于java默认值初始化我应该得到以下输出吗?
“这里x是0”。
没有。您没有看到该输出,因为您首先得到编译时错误。最终变量确实得到一个默认值,但是Java语言规范(JLS)要求你在构造函数的末尾初始化它们(LE:我在这里包括初始化块),否则你会得到一个编译时错误将阻止您的代码被编译和执行。
您的第二个示例尊重要求,这就是为什么(1)您的代码编译和(2)您获得预期的行为。
将来尝试熟悉JLS。关于Java语言没有更好的信息来源。