53

Case 1

class Program {
    static final int var;

    static {
        Program.var = 8;  // Compilation error
    }

    public static void main(String[] args) {
        int i;
        i = Program.var;
        System.out.println(Program.var);
    }
}

Case 2

class Program {
    static final int var;

    static {
        var = 8;  //OK
    }

    public static void main(String[] args) {
        System.out.println(Program.var);
    }
}

Why does Case 1 cause a compilation error?

Andrew Marshall
  • 89,426
  • 20
  • 208
  • 208
Ravi
  • 28,657
  • 41
  • 110
  • 158
  • Case 2 works, so why care? – John Dvorak Dec 08 '12 at 14:58
  • It definitly _looks_ like access to a foreign final (which is illegal). – John Dvorak Dec 08 '12 at 14:59
  • sorry, i didn't mentioned, it was interview question. – Ravi Dec 08 '12 at 14:59
  • i know, case 2 works.. i checked, but my question is why ? – Ravi Dec 08 '12 at 15:00
  • 2
    Very tricky and good question, giving work to brain now.. :) – Pradeep Simha Dec 08 '12 at 15:05
  • i was waiting for other answers, but seems like, none have the better answer than this. Nevertheless, thanks for contribution :) – Ravi Dec 08 '12 at 15:23
  • I cant delete my previous comment. – someone Dec 08 '12 at 15:35
  • @coders Good question.. Looking for a good answer to this too... wait for some time i feel.. – KDjava Dec 08 '12 at 16:00
  • All that i was able to find: for statics, final has to be initialized before the attributes are loaded into the memory, ie; before the classloader finishes loading the class. How can we relate it to accessing the static variables by class name .. I'm not really able to make out.. – KDjava Dec 08 '12 at 17:14
  • 11
    @RohitJain I would not call that a great interview question to be honest: you can't draw any conclusions on the level of a programmer based on whether he does or does not know the answer to that question! – assylias Dec 11 '12 at 21:45
  • 12
    This is one of the most useless questions that I have ever heard :) I do not understand why ppl try to make compilers out of programmers. I would better ask about Balanced Search Trees and Bloom Filters :) – Timofey Dec 11 '12 at 23:05
  • Good Question .. – Ashraf.Shk786 May 21 '18 at 00:04

2 Answers2

43

The JLS holds the answer (note the bold statement):

Similarly, every blank final variable must be assigned at most once; it must be definitely unassigned when an assignment to it occurs. Such an assignment is defined to occur if and only if either the simple name of the variable (or, for a field, its simple name qualified by this) occurs on the left hand side of an assignment operator. [§16]

This means that the 'simple name' must be used when assigning static final variables - i.e. the var name without any qualifiers.

arshajii
  • 118,519
  • 22
  • 219
  • 270
  • which means either `this.var2=10;` or `var2=10;` will be the valid one for assignment. Correct ?? – Ravi Dec 08 '12 at 15:18
  • @coders In this case you can't use `this` because you're in a `static` initializer block. – arshajii Dec 08 '12 at 15:20
  • 7
    @djechlin - the rationale is 1) this simplifies the specification and 2) `ThisClass.someFinal = value;` is an obscure and unnecessary alternative for `someFinal = value;`; i.e. there is no *need* to support it. – Stephen C Dec 08 '12 at 15:25
  • 5
    @coders The class is not available by name until all initialisation is complete, so Program (by that name) does not exist until the static initialiser has been completed. – xagyg Dec 08 '12 at 15:29
  • 7
    @xagyg.. I'm afraid that is not the reason. Class is always first loaded and then the initialization process takes place. A static variable is attached to a class only, so it doesn't make sense that we can't use `class name`. You can yourself see it in practice. Just remove the `final` keyword from the variable, and the 1st code will compile fine. – Rohit Jain Dec 08 '12 at 16:13
  • 5
    @StephenC: It actually complicates the spec since it's an exception from the general access rules for static variables. The sentence in bold could have been omitted without this special rule. I assume the rationale for this might rather be that it simply had been implemented this way in an earlier compiler and then made it into the spec. There are a few other rules in later versions of the language spec where existing slightly obcure behaviour of the compiler was explicitely defined to be correct. – x4u Dec 08 '12 at 17:42
  • Here's an example of how this rule makes it harder to make a decompiler: https://github.com/Storyyeller/Krakatau/issues/14 – Janus Troelsen Jun 10 '13 at 00:19
  • 1
    Sorry for coming late to the party, but I don't think the code violates this bit of the JLS at all. The JLS quote just says that `Program.var = 8` isn't "such an assignment", i.e., the sort of assignment for which it needs to be definitely unassigned first. – chiastic-security Dec 16 '14 at 14:24
  • @chiastic-security Implicit in the quote is the fact that something like `Program.var = 8` isn't a valid statement with respect to the definitions the JLS lays out (since it doesn't fall under any of those definitions). Therefore, the statement itself is nonsensical in the eyes of the JLS, hence the error. – arshajii Dec 17 '14 at 00:33
  • @arshajii: It's not non-sensical at all, it's accessing a field through a primary as laid out in [§15.11](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.11), specifically 15.11.1. The fact §16 then overrides that with special limitations on `final`s is surprising (but there's no question that it does, as your answer shows). – T.J. Crowder Feb 17 '17 at 16:42
7

Apparently this is a cheap syntactic trick to limit definite (un)assignment analysis within the class itself.

If the field is syntactically qualified with a class name, the code is usually in another class, where the analysis cannot reach.

This trick fails in your example. Other examples of oddity:

static class A
{
    static final int a;
    static
    {
        // System.out.println(a); // illegal
        System.out.println(A.a);  // compiles!
        a = 1;
    }
}

If they had more resources, they probably would've made a finer rule. But we can't change spec now.

irreputable
  • 42,827
  • 9
  • 59
  • 89
  • 2
    Well, I think the spec _could_ be changed without causing problems because allowing `Program.var` does not make any currently valid program code invalid, i.e. it is a compatible change, or am I wrong? – siegi Dec 11 '12 at 21:58
  • :-D no problem, just thought I'm missing something... Thanks for the answer! – siegi Dec 12 '12 at 06:56
  • @siegi: Expanding the class of programs which the compiler accepts will increase the amount of fielded code which won't be usable with older installations of the compiler. If the fielded code won't work on older compilers because it's using valuable new language features that the old compiler doesn't understand, that's an acceptable trade-off, it is *is* a trade-off. Allowing new syntactic forms without a major advantage will impose costs to owners of old installations, while offering them little in return. – supercat Oct 28 '13 at 23:09
  • @supercat: Of course you are right, changing the language without much benefit is not good. On the other hand: If the language change coincides with a new major version (e.g. Java 6 to Java 7) you need a new compiler and runtime environment anyway, at least if you want to use any of the new features or any library compiled for the new version… – siegi Nov 29 '13 at 16:38
  • The scary thing here is that if you add `System.out.println(A.a);` after `a = 1;` above, and throw in a `main`, it will actually access and print out 0 and then 1: http://ideone.com/Is80fA Youch. Observing a final before its final value. – T.J. Crowder Feb 17 '17 at 16:59