-->

Can a java subclass's private final field be i

2019-02-27 08:07发布

问题:

I have a pair of classes looking like this;

public abstract class Class1 {

//...

    public Class1() {
        //...
        function2();
        //...
    }

    protected abstract void function2();

}

public class Class2 implements Class1 {

    private final OnSomethingListener mOnSomethingListener = new OnSomethingListener() {
        @Override
        onSomething() {
            doThatOtherThing();
        }
    }

    protected void function2() {
        //uses mOnSomethingListener
        //however mOnSomethingListener is null when this function is called from super()
        //...
    }

    public Class2() {
        super();
    }
}

I assume the listener is null because I am effectively referencing it from super() and it hasn't instantiated yet. However, I want to make it final because, well, it is. Can I get this field (the listener) to initialize in time without putting it in the superclass (which won't be using the listener, ever)?

回答1:

Your design is an instance of the "leaked this problem" and is an anti-pattern in Java. You should never call out to a publicly overridable method from a constructor.



回答2:

Short answer - no. A superclass' constructor, field initialisers and instance initialisers are always called before the subclass'.

The order of calls is formally defined in section 8.8.7.1 of the JLS. Summarising the relevant last parts (where S is the superclass and C is the subclass):

After determining the immediately enclosing instance of i with respect to S (if any), evaluation of the superclass constructor invocation statement proceeds by evaluating the arguments to the constructor, left-to-right, as in an ordinary method invocation; and then invoking the constructor.

Finally, if the superclass constructor invocation statement completes normally, then all instance variable initializers of C and all instance initializers of C are executed. If an instance initializer or instance variable initializer I textually precedes another instance initializer or instance variable initializer J, then I is executed before J.

So when the superclass constructor runs, the subclass and all its fields are completely uninitialised. It's bad practice to call overridden methods from a constructor for this reason. You're essentially letting a reference to the object "escape" from its constructor, which means all the guarantees of construction are off (including things like final fields changing value, etc.).

Calling an abstract method from a constructor is almost always the wrong thing to do. Depending on the implementation of the method in the subclass(es) you might get away with it in some case (i.e. if the method does not depend on any state at all), but it will almost certainly cause a hard-to-debug failure at some point.

For example, would you expect there to be a difference between:

protected String function2() {
    return "foo";
}

and

private final String foo = "foo";

protected String function2() {
    return foo;
}

Analysing problems like this is hard, because they break the mental model of how classes work. Best to avoid this situation altogether.



回答3:

super classes are always initialised before subclasses. Only static fields of a sub class can be initialised first.

You can call an overridden method from the super class which then accesses a field which is not initialised. This is considered bad practice.