0

Is there any way to implement preliminary calculations before an invocation of super(...) or this(...) constructor? Consider the following example:

public class Test {
    private final int n;
    private final int m;
    private final int[] store;

    public Test(int n, int m) {
        /* This is common (most generic) constructor of the class Test.
         It is desirable to invoke it via this(...) call
         from any other constructor of this class
         since it contains some common initialization tasks,
         which are better to concentrate in one place instead
         of scattering them throught the class, due to
         maintainability and security reasons. */
        this.n = n;
        this.m = m;
        store = new int[n];
        // ...
    }

    public Test(Object data) {
        /* This is specific constructor. It depends on some parameters
         which must be processed prior to a call of the Test(int n, int m)
         constructor to get its arguments. The problem is, this processing
         cannot appear here because a call to this(...) must be the first
         statement in a constructor. */
        int a; // Must be calculated via lengthy code
        int b; // Must be calculated via lengthy code
        this(a, b); // <- Compiler error here
        // ... further initialization
    }
}

How can I implement the parameters calculation? The simplest solution is to replace the specific constructor by a static method. But what if it must be a constructor and nothing else (e.g. there is a chance that it may be used in a descendant class). The best solution I've found so far is to introduce a static inner class containing all parameters of the common constructor and use it to store the parameters at the beginning of the specific constructor:

public class Test {
    private final int n;
    private final int m;
    private final int[] store;

    protected static class ConstrParams {
        int nParam;
        int mParam;

        ConstrParams(int n, int m) {
            nParam = n;
            mParam = m;
        }
    }

    protected Test(ConstrParams params) {
        /* This is the common constructor now.
         It is marked as protected because it is essentially auxiliary,
         as well as the class ConstrParams is. */
        n = params.nParam;
        m = params.mParam;
        store = new int[n];
        // ...
    }

    public Test(int n, int m) {
        // This is public interface to the common constructor.
        this(new ConstrParams(n, m));
    }

    private static ConstrParams makeParams(Object data) {
        /* This is a procedure that inserts lengthy calculations
         before constructor chain invocation. */
        int a = 0; // Calculate a from data
        int b = 0; // Calculate b from data
        return new ConstrParams(a, b);
    }

    public Test(Object data) {
        // Specific constructor. Now compiles successfully.
        this(makeParams(data));
        // ... further initialization
    }
}

Is there any better workaround? The case when Test(Object data) must call some super(...) constructor instead of this(...) is even worse since we get less flexibility in this case and often cannot change the ancestor class's code.

John McClane
  • 3,498
  • 3
  • 12
  • 33
  • I'd take a step back and consider the design - as a consumer I would fine it _very_ confusing if a constructor does a lot of CPU work with lengthy code. Consider extracting that logic into a builder and making it explicit. – Benjamin Gruenbaum Jul 28 '18 at 09:37
  • 1
    Also consider static factory methods: `Test test = Test.fromObject(obj)`. When you have several constructors, factory methods are often a good option anyway because they have names. – JB Nizet Jul 28 '18 at 09:38
  • 1
    @Benjamin Gruenbaum That constructor doesn't necessarily consumes lot of CPU time. Just needs some calculations before calling upper constructors and result of these calculations cannot be stored in a single primitive variable. – John McClane Jul 28 '18 at 09:42
  • 1
    Maybe have a private method ```void initialize(int a, int b)```. Call ```initialize``` in both construtors. – zhh Jul 28 '18 at 09:44
  • @JB Nizet I mentioned the "static method" solution in the question. But what if I need a constructor? Is this essentially a language limitation? – John McClane Jul 28 '18 at 09:47
  • You can circumvent it by several means, and your question sums them up. All the options are valid ones. – JB Nizet Jul 28 '18 at 09:50
  • @zhh That's an option, too. But there are some drawbacks: we cannot assign values to final variables inside this method, the method itself must be marked as final to avoid warnings from compiler etc. – John McClane Jul 28 '18 at 09:58

3 Answers3

3

Here's a universal approach that I've found. It allows to inject any code before a call to this(...) or super(...) and thus to overcome Java's limitation of this(...) or super(...) to be the first statement in a constructor.

public class Test {
    private final int n;
    private final int m;
    private final int[] store;

    public Test(int n, int m) {
        // Primary constructor is unchanged
        this.n = n;
        this.m = m;
        store = new int[n];
        // ...
    }

    private static class ConstrParams {
        private int nParam;
        private int mParam;
        /* This class can also be used by more than one constructor
         or independently, to calculate the parameters and store
         them for other purposes. */
        private ConstrParams(Object data) {
            /* Calculate the parameters and/or do any other operations
             (preprocessing) that you would do in the specific constructor prior
             to calling another constructor. You may even add as many auxiliary
             methods as needed into this class and use them in this constructor. */
            nParam = 1;
            mParam = 2;
        }
    }

    /* Intermediate constructor, the main purpose of which is to extract
     parameters (if any) from a ConstrParams object and pass them to a primary
     or an inherited constructor. If ConstrParams produces no parameters but
     makes some pre-this() or -super() actions, this constructor makes
     insertion of such actions available. */
    private Test(ConstrParams params) {
        this(params.nParam, params.mParam);
        /* You can also call super(...) instead of this(...).
         When calling super(...), primary constructor may even not exist. */
//        super(params.nParam, params.mParam);
        /* As the reference to ConstrParams vanishes upon return to the
         calling constructor, you may want to make some actions connected
         with the params object (post-processing) or store the reference to it
         into this object. If so, here's the right place to do it. Otherwise,
         no further action is generally needed in this constructor. */
    }

    public Test(Object data) {
        // Specific constructor. Now compiles successfully.
        this(new ConstrParams(data));
        // ... further initialization
    }
}

Advantages include:

  • The code of constructor being called is unaffected in any way. This is especially useful when using super(...) because the changes to the ancestor class are often undesirable or impossible. When using this(...), the above approach doesn't affect any code relying on primary constructor.
  • It does not depend on the number of parameters the called constructor needs. Just add as many of them as you want as fields of the ConstrParams class and then extract prior to calling primary or inherited constructor. If parameters are computed jointly (i.e. it is impossible or expensive to split their calculations into two or more independent methods), this approach allows to do this. There may be (and often are) cases when the called constructor doesn't take any parameters and you just need to do some action in the dependent constructor prior to this(...) or super(...) call (one example of such action is logging). This solution allows you to make such action.
  • The auxiliary ConstrParams class that produces parameters and/or makes side effects can be used for additional purposes. You can introduce more constructors in it if more than one secondary constructor of the main class needs to overcome the this(...)/super(...) call limitation.
  • Applies uniformly to this(...) and super(...) calls.
John McClane
  • 3,498
  • 3
  • 12
  • 33
2

This solution is applicable for version of Java 8 and higher. I would create another constructor accepting Supplier<Integer>. The method Supplier::get() returns the value:

public Test(Supplier<Integer> n, Supplier<Integer> m) {
    this.n = n.get();
    this.m = m.get();
    store = new int[n.get()];
}

Which might be used in this way:

public Test(Object data) {
    this(() -> {
        int a = data.hashCode();  // expensive calculation
        return a;
    }, () -> {
        int b =  data.hashCode(); // expensive calculation
        return b;
    });
}

This approach would simplify another constructor and leaves only one primary constructor responsible for the encapsulation:

public Test(int n, int m) {
    this(() -> n, () -> m);
}
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • 1
    This has the same drawback as Jorn Vernee's answer: it assumes that `a` and `b` can be computed independently of each other. Besides that, their calculation may be only the "visible" result of pre-constructor code. The "calculator" code may need to produce some side effects. Where to put them, in the first lambda expression or in the second? How can these objects interact to get `b` if it was, in fact, computed during computation of `a` in the first method? – John McClane Jul 28 '18 at 10:32
  • What about `super(...)`? It may not accept `Supplier<>`. – John McClane Jul 28 '18 at 10:40
  • @JohnMcClane: I don't see any `super` in your code and I have no idea where you want to apply it. Provide please more detailed info. – Nikolas Charalambidis Jul 28 '18 at 10:45
  • Just imagine you're extending some class like `HashMap` and need to call its constructor with two parameters like `HashMap(int initialCapacity, float loadFactor)`. But your, more intricate constructor, needs to compute these parameters first. – John McClane Jul 28 '18 at 11:01
  • Then you can call `super(n.get(), m.get());` in the primary constructor with suppliers. – Nikolas Charalambidis Jul 28 '18 at 11:12
  • Forget about primary constructor in `super(…)` case. It may even not exist. The problem now is in communication between specific constructor in a descendant class (like `Test`) and a constructor in an ancestor class (like `HashMap`). However, you may create some intermediate constructor in `Test`, if you need. That will be a solution, still having the same drawbacks as I pointed above. If primary constructor exists, though, it may be passing far more simple parameters to `super(…)` than the specific, or even call another `super(…)`, so you will need to create one more constructor in `Test`. – John McClane Jul 28 '18 at 11:40
  • The problem between the constructor of `Test` and the constructor of a class `Test` extends from solves another constructor. I really don't understand what do you try to achieve and your description is too confusing. Provide an example of a call you want with `super`. – Nikolas Charalambidis Jul 28 '18 at 12:32
  • Assuming class Test extends from HashMap, but gets `initialCapacity` and `loadFactor` from database, and only one query is allowed. public Test() { //... int initialCapacity = resultSet.getInt("CAPACITY"); float loadFactor = resultSet.getFloat("LOAD_FACTOR"); super(initialCapacity, loadFactor); //... } – John McClane Jul 28 '18 at 12:53
2

You could also create helper methods to compute a and b and call those in the this(...) expression. e.g.:

public Test(Object data) {
    this(computeA(data), computeB(data));
}

private static int computeA(Object data) {
    ...
}

private static int computeB(Object data) {
    ...
}
Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93
  • Nice and straightforward. However, I am generally against `static` methods. – Nikolas Charalambidis Jul 28 '18 at 10:02
  • 1
    Right, we can use this method. But that is applicable only if `a` and `b` are computed independently of each other. In practice, they are usually obtained as a result of a mixed procedure, and the bad idea is to go through this code twice. – John McClane Jul 28 '18 at 10:03
  • @John McClane Just make ```Test(int[] nAndm)``` the common constructor and add a static method to compute both m and n. e.g. ```static int[] compute(Object object)```. – zhh Jul 28 '18 at 10:18
  • @zhh What if one parameter is `double` and the other is `boolean`? Compute an array of Objects and typecast them afterwards? – John McClane Jul 28 '18 at 10:24
  • @John McClane Use a wrapper class to wrap all parameters (instead of a int array) as long as you can compute all parameters in one method. Well, this looks like your approach. :P – zhh Jul 28 '18 at 10:26
  • @zhh I've already done so above in the `ConstrParams` class and `makeParams` method. I'm asking about better alternatives. – John McClane Jul 28 '18 at 10:28