8
class Test{

    int p = (p=1) + p;   // ERR "Cannot reference a field before it is defined"
    int q = (q=1) + this.q; //fine!

    void f() {
        int t = (t=1) + t; // fine!
    }       
}

In the first case I understand that: when assignment (or subsequent addition?) is performed, p is treated as not declared.

But why is it different within a method? OK t is not treated as uninitialized because (t=1) is performed before addition. OK, t is not a field, but it is also not declared at the moment!

Can I understand it somehow? Or I shall just memorize this difference?

Maybe this is also related a bit to the same:

    static int x = Test.x + (x=1) + Test.x; // produces 2

    void f() {
       int y = y + (y=1) + y;  // ERR  local variable y may not have been initialized
   }

Why 2? First (x=1) is somehow evaluated (x is not declared!!!), then it returns 1, now x is already assigned (!?) and contains 1, so both Test.x is 1, but (x=1) operator also returned 1 so result shall be 1 + 1 + 1 and 3 shall be (reassigned) into x as a result of evaluating Test.x + (x=1) + Test.x expression.

PARTIAL ANSWER: Actually, the results are implementation specific. JLS guarantees only the order in which operands of a binary operator are evaluated (left-to-right). But if we have binary operators (say, plus) with same priority, their order of evaluation is NOT guaranteed. In my case plus operators are evaluated left-most first, this is why static "int x = Test.x (ZERO) + (x=1) + Test.x (IS 1 after (x=1));" is 0 + 1 + 1 (remember, x=1 is an operator that returns assigned value). Again in my case within method "int y = y + (y=1) + y;" leftmost plus operator is evaluated first (giving error), but if JVM chose to evaluate second plus operator first, then it is guaranteed to evaluate its left operand first and (y=1) would make the y variable initialized (so the code would compile!)

I am still not sure why (x=1) is not treated as undeclared with fields. I vaguely remember that JLS allows undeclared variable in LHS (so any assignment works), but not in RHS (x++, int sth=x). I can memorize it using the following snippet:

class Test {

    { x = 7; }  // fine! Initializer is like a regular method
    int x;

    static { y = 7; }  // fine! Initializer is like a regular method
    static int y;

P.S. This is surely not a duplicate of Default Values and Initialization in Java - there is no direct explanation there. Here we need not only default values (zero for int) rules, but a lot of different rules in a very COMPLEX combination (operator precedence, and especially some rare peculiarities of assignment!). Also I know that assignment precedence is lowest here and that assignment is an operator and it returns value!

Pshemo
  • 122,468
  • 25
  • 185
  • 269
Code Complete
  • 3,146
  • 1
  • 15
  • 38
  • 1
    memorize? no, better avoid both and just use the value it should have – user85421 Jul 11 '19 at 17:12
  • This was an interview question... So for me this was practical. I promise, I would never write such code :) I'm not a maniac. – Code Complete Jul 11 '19 at 17:39
  • I added example with `this.` which lets your scenario compile. Hope you don't mind and this will let others find proper answer. – Pshemo Jul 11 '19 at 17:46
  • 3
    I _hate_ interview questions like that; usually they try to be the "smart" people and in such cases (if I can) I absolutely prove that they are not (read they are idiots). Don't accept the offer - even if you get one, IMHO. – Eugene Jul 11 '19 at 19:48

2 Answers2

1

Read on scope of local variable declarations in Java Language Specification. Your exact problem is described in Example 6.3-2. The description is this:

The following program causes a compile-time error because the initialization of local variable p is within the scope of the declaration of local variable p, but the local variable p does not yet have a value and cannot be used.

  • `p` is not a local variable. – chrylis -cautiouslyoptimistic- Jul 11 '19 at 17:25
  • JLS example you offered is about a conflict about a field and a local variable. Besides it shows that int x = x; is a compile error within a method, but in my case (slightly modified) code compiles fine! – Code Complete Jul 11 '19 at 17:27
  • 4
    another reference: [8.3.3. Restrictions on Field References in Initializers](https://docs.oracle.com/javase/specs/jls/se12/html/jls-8.html#jls-8.3.3) "*For a reference by simple name to an instance variable f declared in class C, it is a compile-time error if:... The reference appears in the initializer of f's own declarator*"" – user85421 Jul 11 '19 at 17:31
  • @CarlosHeuberger you should post that as an answer, but even if the JLS explains it, I have no idea why inside methods it has to be different. – Eugene Jul 11 '19 at 19:51
  • @carlos that gives us a rule starting it's not allowed, I think the OP (and I) would like to understand why such restriction exists – Ruan Mendes Jul 11 '19 at 20:23
  • @JuanMendes not sure if that is the right corner to ask... but maybe one of the JLS authors is reading this... – user85421 Jul 11 '19 at 21:02
0

Maybe I wont be very detailed but I will give a try, you pointed at very good examples of variable life cycle in Java.

int p = (p=1) + p;   // ERR "Cannot reference a field before it is defined"

in this case p is a class field, when the compiler load a class p is not initialized yet (first scan of a class so p is not loaded yet into memory and cannot be evaluated).

void f() {
        int t = (t=1) + t; // fine!
}

in this case the compiler only load the definition of the function, no matter what is inside (I mean if there are not syntax errors and no the kind of error that every IDE can check this is ok). It might be a strange declaration but it's fine, it's not evaluated until you call the function and you initialize t inline.

static int x = Test.x + (x=1) + Test.x; // produces 2

in this case x is a static variable, static 'things' are loaded before class so you can imagine that your compiler put the x field from where you wrote above everything. In this line you are saying that x is equals to 1, so 1 + 1 = 2. This is like doing something like this

static int x = 1;
x = Test.x + Test.x;
GJCode
  • 1,959
  • 3
  • 13
  • 30
  • but why `static int x = Test.x + (x=1) + x;` does not compile (Assuming it is in class `Test`)? – user85421 Jul 11 '19 at 21:45
  • and `int p = (p=1) + this.p` does compile? – user85421 Jul 11 '19 at 21:51
  • this is a different scenario, in this case the equivalent would be static int x = 1 + x; x+=Test.x; the first statement will give you the error. In the first statement the right most x in not initialized yet. For static field declaration and inizialization is inline and made it from the compiler on the first scan. – GJCode Jul 11 '19 at 21:51
  • why, it is the exact same as you wrote, just the field reference is not qualified (`x` and `Test.x` are the same class variable) – user85421 Jul 11 '19 at 21:52
  • yeah but there is a difference, Test.x is a reference to the static field x and this is why it doesn't give you an error, Test.x is evaluated after x is initialized and it compiles because Test has a static field x – GJCode Jul 11 '19 at 21:55
  • `x` is a reference to the same static field as `Test.x` - it is just not allowed: [8.3.3. Restrictions on Field References in Initializers](https://docs.oracle.com/javase/specs/jls/se12/html/jls-8.html#jls-8.3.3): "*For a reference by **simple name** to a class variable f declared in class or interface C, it is a compile-time error if:...The reference appears either in the initializer of f's own declarator*" – user85421 Jul 11 '19 at 21:57
  • I agree with that but are not evaluated at the same time, here the difference is when the compiler load variable and when they are loaded in memory, I know that x and Test.x are the same field but the compiler is smart enough to split things base on some compiling rules, it's like he's sayng ok I have a static variable here let's put it before anything else and initialize it, = sign is an signment and plus is an operation so he can understand that you want to initialize x with one (refering to my answer) – GJCode Jul 11 '19 at 22:02
  • documentation of that compiler smartness, please? and not being evaluated at same time? I doubt the compiler generates different code for using `x` and `Test.x` – user85421 Jul 11 '19 at 22:05
  • maybe I'm not so good at english, I'm not sayng that it generates different code, what I'm sayng is that when a compiler reach that line of code it sees Test.x as a reference to Test's x field (Test has x so it's ok, it will compile) but when you say just x or more specifically (x=1) + x it's doesn't know what x is and throws the error – GJCode Jul 11 '19 at 22:23
  • that makes no sense, the compiler knows exactly that `Test.x` and `x` are the same; and `(x=1)` is another story - probably too late to discuss this (my point is that the Java Language Specification simply does not allow it {to avoid too smart compilers? or maybe for the component in front of the keyboard?}) – user85421 Jul 11 '19 at 22:30