2

I have two cases below.

Case 1:

class A{
    A(){
        System.out.println("A's Constructor");
        f();
    }
    void f() {System.out.println("A");}
}

class B extends A{
    B(){
        System.out.println("B's Constructor");
        f();
    }
    final String x = "B";
    void f() {System.out.println(x);}
}

public class JavaPOCSamples {
    public static void main(String[] args) {
        new B();
    }
}

Case 2:

class A{
    A(){
        System.out.println("A's Constructor");
        f();
    }
    void f() {System.out.println("A");}
}

class B extends A{
    B(){
        System.out.println("B's Constructor");
        f();
    }
    final String x = "B".trim();
    void f() {System.out.println(x);}
}

public class JavaPOCSamples {
    public static void main(String[] args) {
        new B();
    }
}

The difference between case 1 and case 2 is that case 2 has final String x = "B".trim() in class B but case 1 has final String x = "B" in class B.

The output of case 1:

A's Constructor
B // Note that a final variable X is "B"
B's Constructor
B // Note that a final variable X is still "B"

The output of case 2:

A's Constructor
null // Note that a final variable X is null
B's Constructor
B // Note that a final variable X is changed to "B"

The question is regarding case 2 output: why does final String x hold two different values, one is null and another one is string "B"?

kaya3
  • 47,440
  • 4
  • 68
  • 97
user3742125
  • 617
  • 2
  • 9
  • 31

2 Answers2

3

This is a nice puzzle related to the fine details of Java's final field semantics, and instance creation semantics. Like any other field, a final field is initialised to a default value when the object is created (JLS §4.12.5):

Every variable in a program must have a value before its value is used:

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

The final is merely a compile-time check to make sure that the field will be assigned a value exactly once, at the time the object is created. At runtime, the field still has its default initial value before it is assigned a new value. However, there is a special case for final variables of primitive types or of type String, whose initializer is a constant expression (§4.12.4):

A constant variable is a final variable of primitive type or type String that is initialized with a constant expression (§15.28). Whether a variable is a constant variable or not may have implications with respect to class initialization (§12.4.1), binary compatibility (§13.1, §13.4.9), and definite assignment (§16 (Definite Assignment)).

A constant expression is defined in (§15.28) to include literals (including String literals), such as "B". The expression "B".trim() is a method invocation expression, so it is not a constant expression.

The remaining detail is that when an instance of B is created, the B() constructor implicitly invokes the superclass constructor A(), and this occurs before the rest of the B() constructor or any initializers in the subclass B are executed (§12.5, emphasis mine):

Just before a reference to the newly created object is returned as the result, the indicated constructor is processed to initialize the new object using the following procedure: ...

  1. This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). ...

  2. Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class.

So in case 2, because the assignment x = "B".trim() is in the subclass B, it is not executed until after the A() constructor has completed. Since the A() constructor calls f(), that method prints out a null value because that's the default value of the field before it has been assigned any other value.

However, in case 1, the special rule for a final field of type String initialized with a constant expression applies, so x is already "B" before the A() constructor is called.

Community
  • 1
  • 1
kaya3
  • 47,440
  • 4
  • 68
  • 97
  • So, if I understand correctly, variable initialization will happen (except for all method invocation) before the class construction. Please correct me if I am wrong. – user3742125 Dec 07 '19 at 04:28
  • I am not sure what you mean by "class construction". – kaya3 Dec 07 '19 at 23:24
  • I meant to say that before the object creation – user3742125 Dec 08 '19 at 14:39
  • It happens during object creation, but after the superclass constructor is called. – kaya3 Dec 08 '19 at 23:15
  • but constant expression will be evaluated and initialised before the superclass constructor is called. Right??. Also, since B.trim() is a method invocation expression it will be evaluated and initialised only after superclass constructor is called.Right?? – user3742125 Dec 10 '19 at 01:40
  • Since the evaluation of a constant expression can never have side-effects, the order of evaluation doesn't strictly matter; the rule is that a constant variable can never be observed to have its default initial value, even by "devious programs" (in the JLS's words). – kaya3 Dec 10 '19 at 01:47
  • Trying to understand - “constant variable can never be observed to have its default initial value”. Could you please elaborate with an example. – user3742125 Dec 10 '19 at 07:44
  • Well, the code in your question is an example - the variable `x` is a constant when its initializer is the string literal `"B"`, so it cannot be observed by the `f()` method to have its default initial value of `null`, even though `f()` is called before the fields of the class `B` are supposed to be initialized. – kaya3 Dec 10 '19 at 12:52
  • You mentioned that constant expression can never have side-effects. Are there any side-effects if I use a method invocation expression like "B".trim().? If yes then could you please explain it with a simple example. – user3742125 Dec 10 '19 at 15:54
  • The method `.trim()` has no side-effects, but the JLS rules for instance initialization don't treat non-constant expressions differently depending on whether they have side-effects or not. Actually, the Java compiler doesn't infer the side-effect-free-ness of methods at all. – kaya3 Dec 10 '19 at 16:14
  • You mentioned in your answer (last part) that Method invocation expression is not executed until after the A() constructor has completed. So I believe that compiler has a check internally whether initialiser is a constant expression or not.Right? If it is a non constant expression then initialisation will happen only after super class construction. Right ?? – user3742125 Dec 11 '19 at 02:02
  • As noted in my answer, the JLS has a special rule for `final` fields initialised by constant expressions, so yes, the compiler has to check for that in order to have the correct behaviour. – kaya3 Dec 11 '19 at 02:04
0

final should always contain a value, not an object level computation unless the state is established.

You can set final int x = 2 + 2. It would work, but invoking a method needs state and B does not have that yet.

You can verify this by moving final int x = "B".trim() in class A. It would work as A already has state when f() is invoked.

Sunil Dabburi
  • 1,442
  • 12
  • 18