Laurent B
Laurent B

Reputation: 2220

final variable in constructor with inherited call

I just discovered something quite weird. If a final variable is called from the implicit super constructor using an overriden method, the element will never not be initialiazed upon call :

public static abstract class A {

    public A() 
    {
        doSomething();
    }

    public abstract void doSomething();

}

public static class B extends A {

    private final Object s = new Object(); 

    public B()
    {
    }

    public void doSomething() {
        System.out.println(s);
    }
}

public static void main( String[] args )
{
    new B();// prints 'null'
}

If the method is not overriden, the final variable will be correctly instanciated :

public static class B  {

    private final Object s = new Object(); 

    public B()
    {
        doSomething();
    }

    public void doSomething() {
        System.out.println(s);
    }
}

public static void main( String[] args )
{
    new B(); // prints the object correctly
}

Finally, even stranger for me (i think this is relative to the String#intern mechanism)

public static abstract class A {

    public A() 
    {
        doSomething();
    }

    public abstract void doSomething();

}

public static class B extends A {

    private final String s = "Hello"; 

    public B()
    {
    }

    public void doSomething() {
        System.out.println(s);
    }
}

public static void main( String[] args )
{
    new B(); // will print "Hello"
}

My question is what can i do in the first case to fix this, should i use a getter that ensures non-null value ?

I sort of understand why the first case occurs (the constructor implicitely calls the 'super' constructor before initialization of any instance vars), but, if i am correct, in this case why is the 3rd case prints correctly 'Hello' ?

Upvotes: 2

Views: 316

Answers (1)

aioobe
aioobe

Reputation: 421350

It's important to understand that constructors of base classes are executed before constructors of subclasses. This means than fields of subclasses may not have been initialized during construction of base classes. (They will however be initialized during construction of the subclasses.)

My question is what can i do in the first case to fix this, should i use a getter that ensures non-null value ?

The problem you've discovered is one of the reasons to never ever call overridable methods from within the constructor.

A getter is probably just as bad, since the getter would also be overridable.

Instead of having

Object s = new Object();

...

public void doSomething() {
    System.out.println(s);
}

in B, you can pass the variable to be used in the construction of A as an argument to As constructor:

public B() {
    super(new Object());
}

This passes the data relevant for constructing the B object, so that the constructor of B is "self-contained". This is quite messy though, and I would advice you to reconsider the structure of your classes.


Regarding the third case:

private final String s = "Hello";

Since "Hello" is a compile time constant expression, and since s is final, the Java compiler is free to inline the use of s, i.e. replace s with "Hello" at it's discretion.

Upvotes: 2

Related Questions