4

I found happening with the following code made my jaw drop:

public class MCVE {

    { // instance initializer
        System.out.println(test); // cannot reference a field before it is defined
        System.out.println(this.test);
    }
    private final String test = "wat";
}

The line System.out.println(test); is giving the error

Cannot reference a field before it's defined.

But the line System.out.println(this.test); is not

Why does this not error when I qualify it?

Community
  • 1
  • 1
Vogel612
  • 5,620
  • 5
  • 48
  • 73
  • 1
    The problem is not specific to lambdas, an instance initializer will have the same problem. A normal constructor however will not have this issue. You can change `private final Runnable r = () -> {` to `{` and will face the same behaviour. – slartidan Jun 20 '15 at 22:32
  • there is no constructor involved ... what point are you trying to make? – Vogel612 Jun 20 '15 at 22:34
  • 3
    You can also change the Runnable lambda to a String field: `private final String test2 = test;` produces the same result (`String test2 = this.test;` works)` – Simon Forsberg Jun 20 '15 at 22:41
  • 2
    http://stackoverflow.com/questions/15820302/recursive-initializer-works-when-i-add-this – Sotirios Delimanolis Jun 20 '15 at 22:44
  • Still a little confused: will it print "wat" or "" in case we delete the unqualified line and run the lambda? Ah I see, as Matt is pointing out http://ideone.com/kGO82z. I guess I was confused with how C++ does it which gives you control on whether this or test would be captured (by value, etc.). – Christopher Oezbek Jun 20 '15 at 22:57
  • @ChristopherOezbek it will print "wat" correctly. final Strings are considered Constant values and are initialized before final Reference-Typed fields. The declaration order is not important for that. Also qualified names will be resolved to the interned String that's created for every hardcoded string on class-load – Vogel612 Jun 20 '15 at 23:00
  • Matt's link indicates otherwise ("" printed) for the lambda case, not your updated answer. – Christopher Oezbek Jun 20 '15 at 23:02
  • @ChristopherOezbek the link in mat's comment on his answer as well as the link in your comment print me `wat` to stdout... then again mat's link doesn't use lambdas.. – Vogel612 Jun 20 '15 at 23:04

2 Answers2

4

As with so many of these questions, it's because the JLS says so.

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.

It is a compile-time error if any of the four requirements above are not met.

In your (failing) example, the "simple name" case is the condition not met. Qualifying the usage (with this) is the loophole that resolves the compile-time error.

Explained another way:

The use of fields inside an initialization block before the line on which they are declared can only be on the left hand side of an expression (i.e. an assignment), unless they are qualified (in your case this.test).

(paraphrased to suit this question more closely)

Community
  • 1
  • 1
Matt Ball
  • 354,903
  • 100
  • 647
  • 710
  • I agree that this is why it the unqualified instance does not work, but it is not correct why the qualified works. This is not a loophole, but rather it is not accessing test during initialization at all. – Christopher Oezbek Jun 20 '15 at 22:40
  • @ChristopherOezbek the qualified reference works specifically because the JLS says so. – Matt Ball Jun 20 '15 at 22:41
  • And how would it work? What value would it access? – Christopher Oezbek Jun 20 '15 at 22:41
  • @ChristopherOezbek java is not an interpreted language.. I'd fare to say that is **is** accessed during initialization, since this is a little special, transforming "simple names" to "qualified names" and accordingly not falling under the accessing rule you seem to try here. – Vogel612 Jun 20 '15 at 22:42
  • @ChristopherOezbek there is only one possible answer. http://ideone.com/kGO82z – Matt Ball Jun 20 '15 at 22:44
-3

Because capturing this is possible at the time that you create the lambda. Capturing test is not (it is not yet defined). Later though, inside the lambda you can access test (via this).

Christopher Oezbek
  • 23,994
  • 6
  • 61
  • 85