Is final ill-defined?

11,171

Solution 1

A very interesting find. To understand it we need to dig into the Java Language Specification (JLS).

The reason is that final only allows one assignment. The default value, however, is no assignment. In fact, every such variable (class variable, instance variable, array component) points to its default value from the beginning, before assignments. The first assignment then changes the reference.


Class variables and default value

Take a look at the following example:

private static Object x;

public static void main(String[] args) {
    System.out.println(x); // Prints 'null'
}

We did not explicitly assign a value to x, though it points to null, it's default value. Compare that to §4.12.5:

Initial Values of Variables

Each class variable, instance variable, or array component is initialized with a default value when it is created (§15.9, §15.10.2)

Note that this only holds for those kind of variables, like in our example. It does not hold for local variables, see the following example:

public static void main(String[] args) {
    Object x;
    System.out.println(x);
    // Compile-time error:
    // variable x might not have been initialized
}

From the same JLS paragraph:

A local variable (§14.4, §14.14) must be explicitly given a value before it is used, by either initialization (§14.4) or assignment (§15.26), in a way that can be verified using the rules for definite assignment (§16 (Definite Assignment)).


Final variables

Now we take a look at final, from §4.12.4:

final Variables

A variable can be declared final. A final variable may only be assigned to once. It is a compile-time error if a final variable is assigned to unless it is definitely unassigned immediately prior to the assignment (§16 (Definite Assignment)).


Explanation

Now coming back to the your example, slightly modified:

public static void main(String[] args) {
    System.out.println("After: " + X);
}

private static final long X = assign();

private static long assign() {
    // Access the value before first assignment
    System.out.println("Before: " + X);

    return X + 1;
}

It outputs

Before: 0
After: 1

Recall what we have learned. Inside the method assign the variable X was not assigned a value to yet. Therefore, it points to its default value since it is an class variable and according to the JLS those variables always immediately point to their default values (in contrast to local variables). After the assign method the variable X is assigned the value 1 and because of final we can't change it anymore. So the following would not work due to final:

private static long assign() {
    // Assign X
    X = 1;

    // Second assign after method will crash
    return X + 1;
}

Example in the JLS

Thanks to @Andrew I found a JLS paragraph that covers exactly this scenario, it also demonstrates it.

But first let's take a look at

private static final long X = X + 1;
// Compile-time error:
// self-reference in initializer

Why is this not allowed, whereas the access from the method is? Take a look at §8.3.3 which talks about when accesses to fields are restricted if the field was not initialized yet.

It lists some rules relevant for class variables:

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 a class variable initializer of C or in a static initializer of C (§8.7); and

  • The reference appears either in the initializer of f's own declarator or at a point to the left of f's declarator; and

  • The reference is not on the left hand side of an assignment expression (§15.26); and

  • The innermost class or interface enclosing the reference is C.

It's simple, the X = X + 1 is caught by those rules, the method access not. They even list this scenario and give an example:

Accesses by methods are not checked in this way, so:

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);
    }
}

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

Solution 2

Nothing to do with final here.

Since it is at instance or class level, it holds the default value if nothing gets assigned yet. That is the reason you seeing 0 when you accessing it without assigning.

If you access X without completely assigning, it holds the default values of long which is 0, hence the results.

Solution 3

Not a bug.

When the first call to scale is called from

private static final long X = scale(10);

It tries to evaluate return X * value. X has not been assigned a value yet and therefore the default value for a long is used (which is 0).

So that line of code evaluates to X * 10 i.e. 0 * 10 which is 0.

Solution 4

It's not a bug at all, simply put it is not an illegal form of forward references, nothing more.

String x = y;
String y = "a"; // this will not compile 


String x = getIt(); // this will compile, but will be null
String y = "a";

public String getIt(){
    return y;
}

It's simply allowed by the Specification.

To take your example, this is exactly where this matches:

private static final long X = scale(10) + 3;

You are doing a forward reference to scale that is not illegal in any way as said before, but allows you to get the default value of X. again, this is allowed by the Spec (to be more exact it is not prohibited), so it works just fine

Solution 5

Class level members can be initialized in code within the class definition. The compiled bytecode cannot initialize the class members inline. (Instance members are handled similarly, but this is not relevant for the question provided.)

When one writes something like the following:

public class Demo1 {
    private static final long DemoLong1 = 1000;
}

The bytecode generated would be similar to the following:

public class Demo2 {
    private static final long DemoLong2;

    static {
        DemoLong2 = 1000;
    }
}

The initialization code is placed within a static initializer which is run when the class loader first loads the class. With this knowledge, your original sample would be similar to the following:

public class RecursiveStatic {
    private static final long X;

    private static long scale(long value) {
        return X * value;
    }

    static {
        X = scale(10);
    }

    public static void main(String[] args) {
        System.out.println(scale(5));
    }
}
  1. The JVM loads the RecursiveStatic as the jar's entry point.
  2. The class loader runs the static initializer when the class definition is loaded.
  3. The initializer calls the function scale(10) to assign the static final field X.
  4. The scale(long) function runs while the class is partially initialized reading the uninitialized value of X which is the default of long or 0.
  5. The value of 0 * 10 is assigned to X and the class loader completes.
  6. The JVM runs the public static void main method calling scale(5) which multiplies 5 by the now initialized X value of 0 returning 0.

The static final field X is only assigned once, preserving the guarantee held by the final keyword. For the subsequent query of adding 3 in the assignment, step 5 above becomes the evaluation of 0 * 10 + 3 which is the value 3 and the main method will print the result of 3 * 5 which is the value 15.

Share:
11,171

Related videos on Youtube

Little Helper
Author by

Little Helper

Updated on January 24, 2020

Comments

  • Little Helper
    Little Helper over 4 years

    First, a puzzle: What does the following code print?

    public class RecursiveStatic {
        public static void main(String[] args) {
            System.out.println(scale(5));
        }
    
        private static final long X = scale(10);
    
        private static long scale(long value) {
            return X * value;
        }
    }
    

    Answer:

    0

    Spoilers below.


    If you print X in scale(long) and redefine X = scale(10) + 3, the prints will be X = 0 then X = 3. This means that X is temporarily set to 0 and later set to 3. This is a violation of final!

    The static modifier, in combination with the final modifier, is also used to define constants. The final modifier indicates that the value of this field cannot change.

    Source: https://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html [emphasis added]


    My question: Is this a bug? Is final ill-defined?


    Here is the code that I am interested in. X is assigned two different values: 0 and 3. I believe this to be a violation of final.

    public class RecursiveStatic {
        public static void main(String[] args) {
            System.out.println(scale(5));
        }
    
        private static final long X = scale(10) + 3;
    
        private static long scale(long value) {
            System.out.println("X = " + X);
            return X * value;
        }
    }
    

    This question has been flagged as a possible duplicate of Java static final field initialization order. I believe that this question is not a duplicate since the other question addresses the order of initialization while my question addresses a cyclic initialization combined with the final tag. From the other question alone, I would not be able to understand why the code in my question does not make an error.

    This is especially clear by looking at the output that ernesto gets: when a is tagged with final, he gets the following output:

    a=5
    a=5
    

    which does not involve the main part of my question: How does a final variable change its variable?

    • daniu
      daniu over 6 years
      This way of referencing the X member is like referring to a subclass member before the super class constructor has finished, that's your problem and not the definition of final.
    • Voo
      Voo over 6 years
      Just goes to show that if you want the nitty gritty details of how something works, a basic tutorial isn't the best place to look for.
    • Mark Rotteveel
      Mark Rotteveel over 6 years
      I'm (almost) sure the JLS accounts for this, but I'm too lazy to check.
    • AxelH
      AxelH over 6 years
      Note that this violation occurs during the class loading, so it is most likely specified in the JLS. (I need to take the road so I will try to check this evening...)
    • Ivan
      Ivan over 6 years
      From JLS: A blank final instance variable must be definitely assigned (§16.9) at the end of every constructor (§8.8) of the class in which it is declared; otherwise a compile-time error occurs.
    • AxelH
      AxelH over 6 years
      @Ivan, This is not about constant but about instance variable. But can you add the chapter ?
    • Little Helper
      Little Helper over 6 years
      @AxelH, 8.3.1.2 final Fields
    • Marcos Vasconcelos
      Marcos Vasconcelos over 6 years
      And what you expected? The asisgnemtn of the result of a method be a invalid final initializator?
    • Zabuzard
      Zabuzard over 6 years
      Just as a note: Never do any of this in production code. It is super confusing for everyone if someone starts to exploit loopholes in the JLS.
    • Eric Lippert
      Eric Lippert over 6 years
      FYI you can create this exact same situation in C# as well. C# promises that loops in constant declarations will be caught at compile time, but makes no such promises about readonly declarations, and in practice you can get into situations where the initial zero value of the field is observed by another field initializer. If it hurts when you do that, don't do it. The compiler will not save you.
    • user253751
      user253751 over 6 years
      No, it's defined to do exactly what it is doing.
    • Jeppe Stig Nielsen
      Jeppe Stig Nielsen over 6 years
      Here is the last program converted into C# (at tio.run). As expected by @EricLippert, you observe the readonly field before its eventual assignment, so it is entirely the same as in Java, I guess.
    • Joe
      Joe over 6 years
    • Little Helper
      Little Helper over 6 years
      @Joe, I have edited my response to address your claim of duplication. Do you agree with my argument?
    • Impulse The Fox
      Impulse The Fox over 6 years
      IMHO I don't get why this gets so much attention. final just means the field may only be assigned once. If you access the field during its initialization it is obviously 0 because it's not assigned to anything yet, and the default value for that type is 0 (and not null since it's a primitive).
    • Owen
      Owen about 6 years
      If you can think of a way that the compiler would have to rely on finalness for correctness, then your example would create a real problem. For example, if you could thwart the final-locals-in-enclosing-scope rule. But if you can't do that, then I suspect this might be a cosmetic problem only.
  • AxelH
    AxelH over 6 years
    What's tricky about this is that if you don't assign the value, it won't be assign with the default value, but if you used it to assign itself the "final" value, it will...
  • Zabuzard
    Zabuzard over 6 years
    I don't think that's what OP confuses. What confuses is X = scale(10) + 3. Since X, when referenced from the method, is 0. But afterwards it is 3. So OP thinks the X is assigned two different values, which would conflict with final.
  • AxelH
    AxelH over 6 years
    @Zabuza isn't this explain with the "It tries to evaluate return X * value. X has not been assigned a value yet and therefore takes the default value for a long which is 0." ? It is not said X is assigned with the default value but that the X is "replaced" (please don't quote that term ;) ) by the default value.
  • Zabuzard
    Zabuzard over 6 years
    @Andrew Yes, class variable, thanks. Yeah, it would work if there wouldn't be some extra-rules that restrict such access: §8.3.3. Take a look at the four points specified for class variables (the first entry). The method approach in OPs example is not caught by those rules, therefore we can access X from the method. I would not mind that much. It just depends on how exactly the JLS defines things to work in detail. I would never use code like that, it's just exploiting some rules in the JLS.
  • Zabuzard
    Zabuzard over 6 years
    @Andrew The examples in this paragraph also demonstrate this issue: "Accesses by methods are not checked in this way"
  • Suresh Atta
    Suresh Atta over 6 years
    @AxelH I see what do you mean by that. But that is how it should work otherwise the world collapse ;).
  • Reinstate Monica
    Reinstate Monica over 6 years
    The problem is you can call instance methods from the constructor, something that probably should not have been allowed. On the other hand, assigning locals before calling super, which would be useful and safe, is disallowed. Go figure.
  • Eugene
    Eugene over 6 years
    @Andrew you are probably the only one here that actually mentioned forwards references (that are part of the JLS too). this is so simple without this looong answer stackoverflow.com/a/49371279/1059372
  • Andrew Tobilko
    Andrew Tobilko over 6 years
    good answer! I am just curious about why the spec does allow the second case to compile. Is it the only way to see the "inconsistent" state of a final field?
  • Eugene
    Eugene over 6 years
    @Andrew this has bothered me for quite a lot of time too, I am inclined to think its the C++ or C does it (not idea if this is true)
  • vpalmu
    vpalmu over 6 years
    @Andrew: Because to do otherwise would be to solve the Turing incompleteness theorem.
  • Eric Lippert
    Eric Lippert over 6 years
    @Joshua: I think you are mixing up a number of different concepts here: (1) the halting problem, (2) the decision problem, (3) Godel's incompleteness theorem, and (4) Turing-complete programming languages. Compiler writers do not attempt to solve the problem "is this variable definitely assigned before it is used?" perfectly because that problem is equivalent to solving the Halting Problem, and we know we cannot do so.
  • vpalmu
    vpalmu over 6 years
    @EricLippert: Haha oops. Turing incompleteness and halting problem occupy the same place in my mind.
  • nneonneo
    nneonneo over 6 years
    X = X + 1 doesn't work - but X = RecursiveStatic.X + 1 works just fine. See stackoverflow.com/a/15820005/1204143 for an explanation why.
  • fabian
    fabian over 6 years
    "The first assignment then changes the reference." In this case it's not a reference type, but a primitive type.
  • yshavit
    yshavit over 6 years
    This answer is right, if a bit long. :-) I think the tl;dr is that the OP cited a tutorial that said that "[a final] field cannot change," not the JLS. While Oracle's tutorials are quite good, they don't cover all edge cases. For the OP's question, we need to go to the actual, JLS definition of final -- and that definition does not make the claim (that the OP rightfully challenges) that a final field's value can never change.