为什么尝试打印未初始化的变量并不总是会导致错误消息

时间:2022-08-03 01:46:23

Some may find it similar to the SO question Will Java Final variables have default values? but that answer doesn't completely solve this, as that question doesn't directly print the value of x within instance initializer block.

有些人可能会发现它类似于SO问题Java Final变量是否具有默认值?但是这个答案并没有完全解决这个问题,因为那个问题并没有直接在实例初始化程序块中打印x的值。

The problem arises when I try to print x directly inside the instance initializer block, while having assigned a value to x before the end of the block :

当我尝试在实例初始化程序块内直接打印x时,问题出现了,同时在块结束之前为x分配了一个值:

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

This gives a compile time error stating that variable x might not have been initialized.

这给出了编译时错误,指出变量x可能尚未初始化。

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

Case 2

Instead of directly printing, I am calling a function to print:

我没有直接打印,而是调用一个函数来打印:

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

This compiles correctly and gives output

这正确编译并提供输出

0
7
hi

What is the conceptual difference between the two cases?

这两种情况之间的概念差异是什么?

6 个解决方案

#1


32  

In the JLS, §8.3.3. Forward References During Field Initialization, its stated that there's a compile-time error when:

在JLS中,§8.3.3。前向引用在字段初始化期间,它声明在以下情况下存在编译时错误:

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;

    在使用实例变量之后,类或接口C中的实例变量的声明以文本形式出现;

  • The use is a simple name in either an instance variable initializer of C or an instance initializer of C;

    在C的实例变量初始值设定项或C的实例初始值设定项中使用是一个简单的名称;

  • The use is not on the left hand side of an assignment;

    使用不在作业的左侧;

  • C is the innermost class or interface enclosing the use.

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

The following rules come with a few examples, of which the closest to yours is this one:

以下规则附带一些示例,其中最接近您的是这一个:

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

Accesses [to static or instance variables] by methods are not checked in this way, so the code above produces output 0, because the variable initializer for i uses the class method peek() to access the value of the variable j before j has been initialized by its variable initializer, at which point it still has its default value (§4.12.5 Initial Values of Variables).

通过方法访问[到静态或实例变量]不会以这种方式检查,因此上面的代码生成输出0,因为i的变量初始化程序使用类方法peek()在j之前访问变量j的值由其变量初始化程序初始化,此时它仍然具有其默认值(§4.12.5变量的初始值)。

So, to summarize, your second example compiles and executes fine, because the compiler does not check if the x variable was already initialized when you invoke printX() and when printX() actually takes place at Runtime, the x variable will be assigned with its default value (0).

因此,总而言之,您的第二个示例编译并执行正常,因为编译器在调用printX()时不检查x变量是否已经初始化,并且当printX()实际发生在运行时,x变量将被赋值为它的默认值(0)。

#2


12  

Reading the JLS, the answer appears to be in section 16.2.2:

阅读JLS,答案似乎在第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.

空白的最终成员字段V在块(第14.2节)之前是明确分配的(并且绝对不是未分配的),该块是V范围内的任何方法的主体,并且在V范围内声明的任何类的声明之前。

This means that when a method is called, the final field is assigned to its default value 0 before invoking it, so when you reference it inside the method, it compiles successfully and prints the value 0.

这意味着当调用方法时,在调用方法之前将最终字段分配给其默认值0,因此当您在方法内引用它时,它会成功编译并打印值0。

However, when you access the field outside of a method, it is considered unassigned, hence the compilation error. The following code will also not compile:

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

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

because:

因为:

  • 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.
  • 如果在块中紧接在S之前的语句之后V被[un]赋值,则在块的任何其他语句S之前[未]分配V.

Since the final field x is unassigned before the method invocation, it is still unassigned after it.

由于最终字段x在方法调用之前未分配,因此在它之后仍未分配。

This note in the JLS is also relevant:

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.

请注意,没有任何规则可以让我们得出结论,在作为C中声明的任何构造函数,方法,实例初始化程序或静态初始化程序的主体的块之前,V肯定是未分配的。我们可以非正式地断定V不是绝对未分配的在块之前,它是在C中声明的任何构造函数,方法,实例初始化程序或静态初始化程序的主体,但不需要明确声明此类规则。

#3


4  

The difference is that in the first case you are calling System.out.println from initializer block so the block which is invoked before constructor. In the first line

区别在于,在第一种情况下,您从初始化程序块调用System.out.println,因此在构造函数之前调用该块。在第一行

System.out.println(x);

variable x is not yet initialized so that you get compilation error.

变量x尚未初始化,因此您会收到编译错误。

But in the second case you call instance method which doesn't know if variable has already been initialized so you don't have compilation error and you can see the default value for x

但在第二种情况下,您调用实例方法,该方法不知道变量是否已经初始化,因此您没有编译错误,您可以看到x的默认值

#4


4  

Ok, here is my 2 cents.

好的,这是我的2美分。

We all know that final variables can be initialized only While declaring or later on in constructors. Keeping that fact in mind, let see what happened here so far.

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

No errors Case:

没有错误案例:

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. 

Error case :

错误案例:

When you do that in an initialization block, which you are seeing errors.

当您在初始化块中执行此操作时,您会看到错误。

If you look at the docs of initialization block

如果你看一下初始化块的文档

{
    // whatever code is needed for initialization goes here
}

and

The Java compiler copies initializer blocks into every constructor. Therefore, this approach can be used to share a block of code between multiple constructors.

Java编译器将初始化程序块复制到每个构造函数中。因此,该方法可用于在多个构造函数之间共享代码块。

In compiler's eye, your code is literally equals to

在编译器的眼中,你的代码实际上等于

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

You are using it before even initializing it.

您在初始化之前就已经使用它了。

#5


1  

Case 1 :

情况1 :

Gives you a compile-error,

给你一个编译错误,

Because at System.out.println(x);

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

you are trying to print x which was never initialized.

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

Case 2:

案例2:

Works because you are not directly using any literal values, instead you are calling some method, which is correct.

因为您没有直接使用任何文字值,而是调用某个方法,这是正确的,因此可以正常工作。

General Rule is,

一般规则是,

If you are trying to access any variable which is never initialized then it will give a compilation error.

如果您尝试访问任何从未初始化的变量,那么它将给出编译错误。

#6


0  

We deal here with initializer block. The Java compiler copies initializer blocks into every constructor.

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

The compiler error don't occure in second example, because printing x is in another Frame, please refer to spec.

在第二个示例中不会出现编译器错误,因为打印x在另一个Frame中,请参阅规范。

#1


32  

In the JLS, §8.3.3. Forward References During Field Initialization, its stated that there's a compile-time error when:

在JLS中,§8.3.3。前向引用在字段初始化期间,它声明在以下情况下存在编译时错误:

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;

    在使用实例变量之后,类或接口C中的实例变量的声明以文本形式出现;

  • The use is a simple name in either an instance variable initializer of C or an instance initializer of C;

    在C的实例变量初始值设定项或C的实例初始值设定项中使用是一个简单的名称;

  • The use is not on the left hand side of an assignment;

    使用不在作业的左侧;

  • C is the innermost class or interface enclosing the use.

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

The following rules come with a few examples, of which the closest to yours is this one:

以下规则附带一些示例,其中最接近您的是这一个:

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

Accesses [to static or instance variables] by methods are not checked in this way, so the code above produces output 0, because the variable initializer for i uses the class method peek() to access the value of the variable j before j has been initialized by its variable initializer, at which point it still has its default value (§4.12.5 Initial Values of Variables).

通过方法访问[到静态或实例变量]不会以这种方式检查,因此上面的代码生成输出0,因为i的变量初始化程序使用类方法peek()在j之前访问变量j的值由其变量初始化程序初始化,此时它仍然具有其默认值(§4.12.5变量的初始值)。

So, to summarize, your second example compiles and executes fine, because the compiler does not check if the x variable was already initialized when you invoke printX() and when printX() actually takes place at Runtime, the x variable will be assigned with its default value (0).

因此,总而言之,您的第二个示例编译并执行正常,因为编译器在调用printX()时不检查x变量是否已经初始化,并且当printX()实际发生在运行时,x变量将被赋值为它的默认值(0)。

#2


12  

Reading the JLS, the answer appears to be in section 16.2.2:

阅读JLS,答案似乎在第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.

空白的最终成员字段V在块(第14.2节)之前是明确分配的(并且绝对不是未分配的),该块是V范围内的任何方法的主体,并且在V范围内声明的任何类的声明之前。

This means that when a method is called, the final field is assigned to its default value 0 before invoking it, so when you reference it inside the method, it compiles successfully and prints the value 0.

这意味着当调用方法时,在调用方法之前将最终字段分配给其默认值0,因此当您在方法内引用它时,它会成功编译并打印值0。

However, when you access the field outside of a method, it is considered unassigned, hence the compilation error. The following code will also not compile:

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

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

because:

因为:

  • 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.
  • 如果在块中紧接在S之前的语句之后V被[un]赋值,则在块的任何其他语句S之前[未]分配V.

Since the final field x is unassigned before the method invocation, it is still unassigned after it.

由于最终字段x在方法调用之前未分配,因此在它之后仍未分配。

This note in the JLS is also relevant:

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.

请注意,没有任何规则可以让我们得出结论,在作为C中声明的任何构造函数,方法,实例初始化程序或静态初始化程序的主体的块之前,V肯定是未分配的。我们可以非正式地断定V不是绝对未分配的在块之前,它是在C中声明的任何构造函数,方法,实例初始化程序或静态初始化程序的主体,但不需要明确声明此类规则。

#3


4  

The difference is that in the first case you are calling System.out.println from initializer block so the block which is invoked before constructor. In the first line

区别在于,在第一种情况下,您从初始化程序块调用System.out.println,因此在构造函数之前调用该块。在第一行

System.out.println(x);

variable x is not yet initialized so that you get compilation error.

变量x尚未初始化,因此您会收到编译错误。

But in the second case you call instance method which doesn't know if variable has already been initialized so you don't have compilation error and you can see the default value for x

但在第二种情况下,您调用实例方法,该方法不知道变量是否已经初始化,因此您没有编译错误,您可以看到x的默认值

#4


4  

Ok, here is my 2 cents.

好的,这是我的2美分。

We all know that final variables can be initialized only While declaring or later on in constructors. Keeping that fact in mind, let see what happened here so far.

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

No errors Case:

没有错误案例:

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. 

Error case :

错误案例:

When you do that in an initialization block, which you are seeing errors.

当您在初始化块中执行此操作时,您会看到错误。

If you look at the docs of initialization block

如果你看一下初始化块的文档

{
    // whatever code is needed for initialization goes here
}

and

The Java compiler copies initializer blocks into every constructor. Therefore, this approach can be used to share a block of code between multiple constructors.

Java编译器将初始化程序块复制到每个构造函数中。因此,该方法可用于在多个构造函数之间共享代码块。

In compiler's eye, your code is literally equals to

在编译器的眼中,你的代码实际上等于

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

You are using it before even initializing it.

您在初始化之前就已经使用它了。

#5


1  

Case 1 :

情况1 :

Gives you a compile-error,

给你一个编译错误,

Because at System.out.println(x);

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

you are trying to print x which was never initialized.

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

Case 2:

案例2:

Works because you are not directly using any literal values, instead you are calling some method, which is correct.

因为您没有直接使用任何文字值,而是调用某个方法,这是正确的,因此可以正常工作。

General Rule is,

一般规则是,

If you are trying to access any variable which is never initialized then it will give a compilation error.

如果您尝试访问任何从未初始化的变量,那么它将给出编译错误。

#6


0  

We deal here with initializer block. The Java compiler copies initializer blocks into every constructor.

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

The compiler error don't occure in second example, because printing x is in another Frame, please refer to spec.

在第二个示例中不会出现编译器错误,因为打印x在另一个Frame中,请参阅规范。