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 directl
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
).
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.
Case 1 :
Gives you a compile-error,
Because at System.out.println(x);
you are trying to print x which was never initialized.
Case 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.
We deal here with initializer block. The Java compiler copies initializer blocks into every constructor.
The compiler error don't occure in second example, because printing x is in another Frame, please refer to spec.
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
Reading the JLS, the answer appears to be in 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
.
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 statementS
of the block iffV
is [un]assigned after the statement immediately precedingS
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 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.