35

This fails to compile (with an illegal forward reference error), as one would expect:

class test {
    int x = x + 42;
}

But this works:

class test {
    int x = this.x + 42;
}

What's going on? What gets assigned in the latter case?

2
  • 10
    How odd... (+1) Commented Apr 4, 2013 at 19:53
  • 4
    Reminds me of: while (true) { try { return; } finally { continue; } } Commented Apr 4, 2013 at 20:08

3 Answers 3

18

It is too difficult to discover and forbid all accesses to x during x's initialization. For example

int x = that().x;                |    int x = getX();
                                 |
Test that(){ return this; }      |    int getX(){ return x; }

The spec stops at "access by simple name" and does not try to be more comprehensive.

In another section, "Definite Assignment", the spec does the similar thing. For example

public class Test
{
    static final int y;
    static final int z = y;  // fail, y is not definitely assigned 
    static{ y = 1; }
}

public class Test
{
    static final int y;
    static final int z = Test.y;  // pass... because it's not a simple name
    static{ y = 1; }
}

Interestingly, "Definite Assignment" specifically mentions that this.x is equivalent to x

(or, for a field, the simple name of the field qualified by this)

this clause could be added to the section quoted by NPE as well.

  • the usage is via a simple name (or a simple name qualified by this)

But in the end, it is impossible at compile time to analyze all possible usages/accesses to a field.

Sign up to request clarification or add additional context in comments.

2 Comments

Do you have a view on whether int x = this.x + 42; has well-defined semantics?
the spec probably should forbid it as well, like "the usage is via a simple name or a simple name qualified by this"
17

Summary: Both initializers access a field that's yet to be initialized (and therefore still has the default value of zero). Since this is likely to be a programming error, the language bans some simple forms of such access. However, it does not ban more complex form.

The behaviour is compliant with the JLS, specifically §8.3.2.3. Restrictions on the use of Fields during Initialization

The declaration of a member needs to appear textually before it is used only if the member is an instance (respectively static) field of a class or interface C and all of the following conditions hold:

  • The usage occurs in an instance (respectively static) variable initializer of C or in an instance (respectively static) initializer of C.

  • The usage is not on the left hand side of an assignment.

  • The usage is via a simple name.

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

The first example satisfies all four conditions and is therefore invalid. The second example doesn't satisfy the third condition (this.x is not a simple name), and is therefore OK.

The overall sequence of events is as follows:

Thus if an initializer refers to a field that appears later in the class definition (or to the field itself), it would see the default value of that other field. This is likely to be be a programming error and is therefore explicitly forbidden by §8.3.2.3.

If you circumvent §8.3.2.3 by, for example, using this. to forward-refer to a field, you'll see the default value (zero for int). Thus the following is well-defined and is guaranteed to set x to 42:

class test {
    int x = this.x + 42;
}

Comments

1

In the first case compiler tries to evaluate expression 'x + 42' but fails because x is not initialized.

In the second case expression 'this.x + 42' is evaluated at runtime (because of 'this' keyword), when x is already initialized and has value 0.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.