简体   繁体   中英

Is there any way to initialize member variables of a subclass in Java before the superclass' constructor is called?

I need this because the constructor in the superclass is calling a method which is overridden in the subclass. The method returns a value which is passed to the subclass' constructor. But the superclass constructor must be called before the subclass constructor, so I have no chance to save the value passed in.

Calling an overridden method from the superclass constructor is simply not going to work - don't do it. The superclass constructor must always finish before that of the subclass. While the superclass constructor is executing, the object in question is a (half initialized) instance of the superclass, not the subclass! So if you try to call any overridden function from the constructor, the subclass fields it may depend on are not yet initialized (just as you have observed). This is a fundamental fact of class design, and there is no workaround.

As explained in Effective Java 2nd. Ed. (Chapter 4, Item 17):

There are [...] restrictions that a class must obey to allow inheritance. Constructors must not invoke overridable methods, directly or indirectly. If you violate this rule, program failure will result. The superclass constructor runs before the subclass constructor, so the overriding method in the subclass will get invoked before the subclass constructor has run. If the overriding method depends on any initialization performed by the subclass constructor, the method will not behave as expected.

If you can change the superclass implementation, try moving the call to the virtual function out of the constructor. One way to achieve this is using a Factory Method:

class Super {
    public void init() { ... }
}

class Subclass extends Super {
    private Subclass() { super(); ... }
    public void init() { super.init(); ... }
    public static Subclass createInstance() {
        Subclass instance = new Subclass();
        instance.init();
        return instance;
    }
}

Note that the constructor of Subclass is private to ensure that it can only be instantiated via createInstance() , thus instances are always initialized properly. OTOH this also prevents further subclassing. However, subclassing a concrete class is not recommended anyway - a class meant to be subclassed should be abstract (with a protected constructor in this case). And of course, any further subclasses must also have non-public constructors and static factory methods which diligently call init() ...

Your question is summarized by the following snippet:

abstract class Base {
    Base() {
        dontdoit();
    }
    abstract void dontdoit();
}
public class Child extends Base {
    final int x;
    Child(int x) {
        this.x = x;
    }
    @Override void dontdoit() {
        System.out.println(x);
    }

    public static void main(String args[]) {
        new Child(42); // prints "0" instead of "42"!!!
    }
}

This is a quote from Josh Bloch and Neal Gafter's Java Puzzlers: Traps, Pitfalls, and Corner Cases :

The problem arises whenever a constructor calls a method that has been overridden in its subclass. A method invoked in this way always runs before the instance has been initialized, when its declared fields still have their default values. To avoid this problem, never call overridable methods from constructors , either directly or indirectly [EJ Item 15]. This prohibition extends to instance initializers and the bodies of the pseudoconstructors readObject and clone . (These methods are called pseudoconstructors because they create objects without invoking a constructor.)

In short: DON'T DO IT!

Moreover, the book proposes a possible solution (slightly edited for generality):

You can fix the problem by initializing the field lazily, when it is first used, rather than eagerly, when the instance is created.

You didn't mention whether you can modify the superclass's constructor at all. If you can, then this is doable. As other have said, this is not advisable, but here's a contrived example that does what you're looking for--the superclass calls an overridden method which uses an argument that was passed to the subclass's constructor:

public abstract class Base {
        public Base(int value) {
                init(value);
                System.out.println(getValue());
        }

        public abstract void init(int value);
        public abstract int getValue();
}

public class Derived extends Base {
        private int value;

        public Derived(int value) {
                super(value);
        }
        public void init(int value) {
                this.value = value;
        }
        public int getValue() {
                return value;
        }
        public static void main(String[] argv) {
                new Derived(25);
        }
}

> java Derived
25

I know the problem. It is not possible. If the super class is not your own and you have no other options then you can do some bad hacks. But it is not recommended.

Solution 1:

Use a static factory with a synchronized. This can look like (redudced):

private static String savedValue;

public static Abc create(String value){
  synchronized(Abc.class){
    savedValue = value;
    Abc abc = new Abc();
    savedValue = null;
    return abc;
  }

private Abc(){
}

private value;

String getValue(){
  if( value == null ){
    value = savedValue;
  }
  return value;
}

Solution 2:

Make a differentiation in your method if the own constructor was already run with a flag. Then you need to duplicate some stuff of the super constructor with the real value.

If this is a problem you have a fundamental problem in your class hierarchy; superclasses cannot know about any specific subclass because there might be several, they might even be loaded dynamically at runtime. That is why any super() calls in a constructor must be on the first line.

As a general rule, don't call any public methods in a constructor.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM