55

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.

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 :

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.

$ 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?

Community
  • 1
  • 1
Ashwani Kumar Rahul
  • 776
  • 1
  • 6
  • 14
  • your first class first `System.out.println(x);` give the error as there is no declaration/assign of x before that – Rajarshi Das Nov 30 '15 at 09:39
  • 1
    @RajarshiDas But there is also no declaration in the second case...read carefully (...or is there?) – Tim Biegeleisen Nov 30 '15 at 09:39
  • 2
    @RajarshiDas why would an int value ever be printed as null? – Andy Turner Nov 30 '15 at 09:46
  • @RajarshiDas - it is an `int`, it should never be `null`. ;) – Michael Lloyd Lee mlk Nov 30 '15 at 09:47
  • @RajarshiDas as we know, that the Instance Initializer block will be copied inside the constructor just after super().... so in the second case also x is not initialised before calling the printX() for the first time – Ashwani Kumar Rahul Nov 30 '15 at 09:48
  • 2
    I suspect that the formal answer to this is buried deeply in the [Definite Assignment](https://docs.oracle.com/javase/specs/jls/se7/html/jls-16.html) chapter of JLS. – Andy Turner Nov 30 '15 at 09:48
  • What compiler are you using? My compiler (JDK 1.8 on Windows) gives me an error for both. Ah, now I see why... – mattiash Nov 30 '15 at 09:48
  • @mattiash i used online java compiler http://www.tutorialspoint.com/compile_java_online.php, as well as i used eclipse... same result as i posted – Ashwani Kumar Rahul Nov 30 '15 at 09:50
  • @ShiladittyaChakraborty thanks for the link which u provide.... but its still not clear that if i use a method to print x, the it is ok.... but if i directly print then it is not compiling – Ashwani Kumar Rahul Nov 30 '15 at 09:54
  • @AshwaniKumarRahul JLS is saying that you must assign the default value to blank final instance variable in constructor (or in initialization block which is pretty the same). That is why you get the error in the first case. However it doesn't say that you can not access it in constructor before. Looks weird a little bit, but you can access it before assignment and see default value for int - 0. – Rajarshi Das Nov 30 '15 at 09:57
  • @AshwaniKumarRahul try to read/understand the SO thread share by shiladitya – Rajarshi Das Nov 30 '15 at 09:59
  • 1
    Case1 : System.out.println(this.x); will also remove the compilation error. not sure why. – Anil Bharadia Nov 30 '15 at 10:04
  • @AnilBharadia thanks... yeah sure.... u pointed out another important things.. – Ashwani Kumar Rahul Nov 30 '15 at 10:47
  • @RajarshiDas thanks.... i read the SO link by Shiladitya... its clear that x should be initialised before the end of constructor... but for Case1 also, initialization is done before the end of the contructor, but still it gives error.... Another important things shared by AnilBharadiya in his comment – Ashwani Kumar Rahul Nov 30 '15 at 10:50
  • Can someone simply tell why the first call to `printX()` shows `0` ? – prime Nov 30 '15 at 11:11
  • @prime in general first call to printX() is not required.... this questions is just asked to know the actual concept of how the compiler checks the code... – Ashwani Kumar Rahul Nov 30 '15 at 12:59
  • @AshwaniKumarRahul : so I think the reason is still unclear right ? – prime Dec 01 '15 at 04:40
  • @prime hmm yes... but after reading In the JLS, https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.3.3 one can get some idea, about the things which compiler considers as error and things which it compiles correctly... – Ashwani Kumar Rahul Dec 01 '15 at 07:52

6 Answers6

33

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

Konstantin Yovkov
  • 62,134
  • 8
  • 100
  • 147
  • in case1 if I replace System.out.println(x) with System.out.println(this.x), then also the compilation error will go away, here no method is invoked, or is it ? – Anil Bharadia Nov 30 '15 at 10:13
  • @AnilBharadia I guess you are using Eclipse: it does not compile with `javac` (1.8.0_51) if you use `this.x`. – Tunaki Nov 30 '15 at 10:14
  • 3
    I'm still confused how the quote of the JLS you make apply in this question. In both cases, the declaration of `x` appears textually before its use. – Tunaki Nov 30 '15 at 10:21
  • @Tunaki I checked with javac (1.7.0_67) outside eclipse to compile and its too not giving error when this.x is used – Anil Bharadia Nov 30 '15 at 10:29
  • 1
    The issue in their question doesn't seem to have anything to do with forward references, as @Tunaki states. – Sotirios Delimanolis Aug 23 '17 at 19:42
12

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.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • 2
    The byte-code suggests that your answer is right. Inside the `printX()` method, value `0` is being read using `getField` on the current instance. And before call to `printX()`, invoke special is called and all fields are initialized to their default values. I think both you and Kocko answer one part each of this *two-part-answer* :) – TheLostMind Nov 30 '15 at 10:10
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(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

k0ner
  • 1,086
  • 7
  • 20
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.

Suresh Atta
  • 120,458
  • 37
  • 198
  • 307
1

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.

Manu
  • 1,474
  • 1
  • 12
  • 18
Vishal Gajera
  • 4,137
  • 5
  • 28
  • 55
0

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.