Is it possible to do computation before super() in the constructor?

Viewed 39658

Given that I have a class Base that has a single argument constructor with a TextBox object as it's argument. If I have a class Simple of the following form:

public class Simple extends Base {
  public Simple(){
    TextBox t = new TextBox();
    super(t);
    //wouldn't it be nice if I could do things with t down here?
  }
}

I will get a error telling me that the call to super must be the first call in a constructor. However, oddly enough, I can do this.

public class Simple extends Base {
  public Simple(){
    super(new TextBox());
  }
}

Why is it that this is permited, but the first example is not? I can understand needing to setup the subclass first, and perhaps not allowing object variables to be instantiated before the super-constructor is called. But t is clearly a method (local) variable, so why not allow it?

Is there a way to get around this limitation? Is there a good and safe way to hold variables to things you might construct BEFORE calling super but AFTER you have entered the constructor? Or, more generically, allowing for computation to be done before super is actually called, but within the constructor?

Thank you.

7 Answers

Yes, there is a workaround for your simple case. You can create a private constructor that takes TextBox as an argument and call that from your public constructor.

public class Simple extends Base {
    private Simple(TextBox t) {
        super(t);
        // continue doing stuff with t here
    }

    public Simple() {
        this(new TextBox());
    }
}

For more complicated stuff, you need to use a factory or a static factory method.

It is required by the language in order to ensure that the superclass is reliably constructed first. In particular, "If a constructor does not explicitly invoke a superclass constructor, the Java compiler automatically inserts a call to the no-argument constructor of the superclass."

In your example, the superclass may rely on the state of t at construction time. You can always ask for a copy later.

There's an extensive discussion here and here.

You can define a static supplier lambda which can contain more complicated logic.

public class MyClass {

    private static Supplier<MyType> myTypeSupplier = () -> {
        return new MyType();
    };

    public MyClass() {
        super(clientConfig, myTypeSupplier.get());
    }
}

The reason why the second example is allowed but not the first is most likely to keep the language tidy and not introduce strange rules.

Allowing any code to run before super has been called would be dangerous since you might mess with things that should have been initialized but still haven't been. Basically, I guess you can do quite a lot of things in the call to super itself (e.g. call a static method for calculating some stuff that needs to go to the constructor), but you'll never be able to use anything from the not-yet-completely-constructed object which is a good thing.

That's how Java works :-) There are technical reasons why it was chosen this way. It might indeed be odd that you can not do computations on locals before calling super, but in Java the object must first be allocated and thus it needs to go all the way up to Object so that all fields are correctly initialized before you can modify them.

In your case there is most of the time a getter that allows you to access the parameter you gave to super(). So you would use this:

super( new TextBox() );
final TextBox box = getWidget();
... do your thing...

This is my solution that allows to create additional object, modify it without creating extra classes, fields, methods etc.

class TextBox {
    
}

class Base {

    public Base(TextBox textBox) {
        
    }
}

public class Simple extends Base {

    public Simple() {
        super(((Supplier<TextBox>) () -> {
            var textBox = new TextBox();
            //some logic with text box
            return textBox;        
        }).get());
    }
}
Related