为什么尝试打印未初始化的变量并不总是导致错误消息
Why attempt to print uninitialized variable does not always result in an error message
有些人可能会发现它类似于 SO 问题 Will Java Final variables have default values?,但该答案并未完全解决此问题,因为该问题并未直接在实例初始化程序块中打印 x 的值。
当我尝试在实例初始化块中直接打印 x 时出现问题,同时在块结束之前为 x 赋值:
案例一
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
案例二
我不是直接打印,而是调用一个函数来打印:
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
这两种情况在概念上有什么区别?
案例一:
给你一个编译错误,
因为在 System.out.println(x);
you are trying to print x which was never initialized.
案例二:
之所以有效,是因为您没有直接使用任何文字值,而是调用了一些正确的方法。
一般规则是,
If you are trying to access any variable which is never initialized
then it will give a compilation error.
不同之处在于,在第一种情况下,您从 初始化块 调用 System.out.println
,因此在构造函数之前调用的块。第一行
System.out.println(x);
变量 x
尚未初始化,因此您会遇到编译错误。
但在第二种情况下,您调用的实例方法不知道变量是否已经初始化,因此您没有编译错误,您可以看到 x
[=14= 的默认值]
好的,这是我的 2 美分。
我们都知道final变量只能在声明时或稍后在构造函数中初始化。牢记这一事实,让我们看看到目前为止发生了什么。
无错误案例:
So when you use inside a method, it have already a value.
1) If you initialize it, that value.
2) If not, the default value of data type.
错误情况:
When you do that in an initialization block, which you are seeing errors.
如果你看 docs of initialization block
{
// whatever code is needed for initialization goes here
}
和
The Java compiler copies initializer blocks into every constructor. Therefore, this approach can be used to share a block of code between multiple constructors.
在编译器看来,您的代码实际上等于
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();
}
}
您甚至在初始化之前就在使用它。
阅读 JLS,答案似乎在 section 16.2.2:
A blank final
member field V
is definitely assigned (and moreover is not definitely unassigned) before the block (§14.2) that is the body of any method in the scope of V
and before the declaration of any class declared within the scope of V
.
这意味着当一个方法被调用时,final字段在调用它之前被赋值为默认值0,所以当你在方法内部引用它时,它编译成功并打印值0。
但是,当您在方法之外访问该字段时,它被认为是未分配的,因此会出现编译错误。以下代码也不会编译:
public class Main {
final int x;
{
method();
System.out.println(x);
x = 7;
}
void method() { }
public static void main(String[] args) { }
}
因为:
V
is [un]assigned before any other statement S
of the block iff V
is [un]assigned after the statement immediately preceding S
in the block.
由于最终字段 x
在方法调用之前未分配,因此在方法调用之后仍未分配。
JLS 中的这条注释也是相关的:
Note that there are no rules that would allow us to conclude that V
is definitely unassigned before the block that is the body of any constructor, method, instance initializer, or static initializer declared in C
. We can informally conclude that V
is not definitely unassigned before the block that is the body of any constructor, method, instance initializer, or static initializer declared in C, but there is no need for such a rule to be stated explicitly.
在 JLS 中,§8.3.3. Forward References During Field Initialization,它指出在以下情况下存在编译时错误:
Use of instance variables whose declarations appear textually after the use is sometimes restricted, even though these instance variables
are in scope. Specifically, it is a compile-time error if all of the
following are true:
The declaration of an instance variable in a class or interface C appears textually after a use of the instance variable;
The use is a simple name in either an instance variable initializer of C or an instance initializer of C;
The use is not on the left hand side of an assignment;
C is the innermost class or interface enclosing the use.
以下规则附带了一些示例,其中最接近您的是这个:
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
,因为[的变量初始值设定项=12=] 使用 class 方法 peek()
在 j
被它的变量初始化器初始化之前访问变量 j
的值,此时它仍然有它的默认值 (§4.12.5 Initial Values of Variables).
因此,总而言之,您的第二个示例编译并执行得很好,因为编译器不会在您调用 printX()
和 printX()
时检查 x
变量是否已经初始化实际上发生在运行时,x
变量将被赋予其默认值 (0
)。
我们在这里处理初始化块。 Java 编译器将初始化程序块复制到每个构造函数中。
第二个例子没有出现编译错误,因为在另一个Frame中打印x,请参考规范
有些人可能会发现它类似于 SO 问题 Will Java Final variables have default values?,但该答案并未完全解决此问题,因为该问题并未直接在实例初始化程序块中打印 x 的值。
当我尝试在实例初始化块中直接打印 x 时出现问题,同时在块结束之前为 x 赋值:
案例一
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
案例二
我不是直接打印,而是调用一个函数来打印:
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
这两种情况在概念上有什么区别?
案例一:
给你一个编译错误,
因为在 System.out.println(x);
you are trying to print x which was never initialized.
案例二:
之所以有效,是因为您没有直接使用任何文字值,而是调用了一些正确的方法。
一般规则是,
If you are trying to access any variable which is never initialized then it will give a compilation error.
不同之处在于,在第一种情况下,您从 初始化块 调用 System.out.println
,因此在构造函数之前调用的块。第一行
System.out.println(x);
变量 x
尚未初始化,因此您会遇到编译错误。
但在第二种情况下,您调用的实例方法不知道变量是否已经初始化,因此您没有编译错误,您可以看到 x
[=14= 的默认值]
好的,这是我的 2 美分。
我们都知道final变量只能在声明时或稍后在构造函数中初始化。牢记这一事实,让我们看看到目前为止发生了什么。
无错误案例:
So when you use inside a method, it have already a value.
1) If you initialize it, that value.
2) If not, the default value of data type.
错误情况:
When you do that in an initialization block, which you are seeing errors.
如果你看 docs of initialization block
{
// whatever code is needed for initialization goes here
}
和
The Java compiler copies initializer blocks into every constructor. Therefore, this approach can be used to share a block of code between multiple constructors.
在编译器看来,您的代码实际上等于
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();
}
}
您甚至在初始化之前就在使用它。
阅读 JLS,答案似乎在 section 16.2.2:
A blank
final
member fieldV
is definitely assigned (and moreover is not definitely unassigned) before the block (§14.2) that is the body of any method in the scope ofV
and before the declaration of any class declared within the scope ofV
.
这意味着当一个方法被调用时,final字段在调用它之前被赋值为默认值0,所以当你在方法内部引用它时,它编译成功并打印值0。
但是,当您在方法之外访问该字段时,它被认为是未分配的,因此会出现编译错误。以下代码也不会编译:
public class Main {
final int x;
{
method();
System.out.println(x);
x = 7;
}
void method() { }
public static void main(String[] args) { }
}
因为:
V
is [un]assigned before any other statementS
of the block iffV
is [un]assigned after the statement immediately precedingS
in the block.
由于最终字段 x
在方法调用之前未分配,因此在方法调用之后仍未分配。
JLS 中的这条注释也是相关的:
Note that there are no rules that would allow us to conclude that
V
is definitely unassigned before the block that is the body of any constructor, method, instance initializer, or static initializer declared inC
. We can informally conclude thatV
is not definitely unassigned before the block that is the body of any constructor, method, instance initializer, or static initializer declared in C, but there is no need for such a rule to be stated explicitly.
在 JLS 中,§8.3.3. Forward References During Field Initialization,它指出在以下情况下存在编译时错误:
Use of instance variables whose declarations appear textually after the use is sometimes restricted, even though these instance variables are in scope. Specifically, it is a compile-time error if all of the following are true:
The declaration of an instance variable in a class or interface C appears textually after a use of the instance variable;
The use is a simple name in either an instance variable initializer of C or an instance initializer of C;
The use is not on the left hand side of an assignment;
C is the innermost class or interface enclosing the use.
以下规则附带了一些示例,其中最接近您的是这个:
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
,因为[的变量初始值设定项=12=] 使用 class 方法 peek()
在 j
被它的变量初始化器初始化之前访问变量 j
的值,此时它仍然有它的默认值 (§4.12.5 Initial Values of Variables).
因此,总而言之,您的第二个示例编译并执行得很好,因为编译器不会在您调用 printX()
和 printX()
时检查 x
变量是否已经初始化实际上发生在运行时,x
变量将被赋予其默认值 (0
)。
我们在这里处理初始化块。 Java 编译器将初始化程序块复制到每个构造函数中。
第二个例子没有出现编译错误,因为在另一个Frame中打印x,请参考规范