Why attempt to print uninitialized variable does not always result in an error message

Solution 1:

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

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.

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).

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).

Solution 2:

Reading the JLS, the answer appears to be in 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.

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.

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.

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

This note in the JLS is also relevant:

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.

Solution 3:

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

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

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

Solution 4:

Ok, here is my 2 cents.

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.

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.