Calculate members during construction in records
Asked Answered
H

3

12

I've created a Java record, and would like to have a constructor that takes in a reduced number of arguments compared to the default constructor, and calculate and initialise all members based on the given arguments.

However, I've found this difficult to achieve given the first line of the custom constructor must call the default constructor. My current approach is to call my calculation functions as needed, but this results in unnecessary processing.

Surely there must be a better way to achieve this?

public record MyRecord(double a, double b, double c) {
     public MyRecord(double a) {
          this(a, calculateB(a), calculateC(a, calculateB(a)));
     }

     private static double calculateB(double a) {
          // Some calculations 
          return b;

     }

     private static double calculateC(double a, double b) {
          // Some calculations 
          return c;
     }
}

Haldan answered 28/2, 2022 at 14:14 Comment(2)
I have a strong preference for using just the null constructor in objects and using setters to avoid situations like thisCurrish
@Currish The question is about records, which are intended to be immutable.Alexandrite
B
19

An alternative solution is to add a static factory method to your record instead of an additional constructor.

For example:

public record MyRecord(double a, double b, double c) {

    public static MyRecord newMyRecord(double a) {
        var b = calculateB(a);
        var c = calculateC(a, b);
        return new MyRecord(a, b, c);
    }


    private static double calculateB(double a) {
        // Some calculations 
        return b;

    }

    private static double calculateC(double a, double b) {
        // Some calculations 
        return c;
    }
}
Biosynthesis answered 28/2, 2022 at 14:24 Comment(0)
E
2

The constructor’s first statement must be a constructor invocation, but it doesn’t have to be the canonical constructor. This allows to construct a chain which ends up at the canonical constructor, but introduce local variables (parameters) in-between to avoid redundant calculations:

public record MyRecord(double a, double b, double c) {
     public MyRecord(double a) {
          this(a, calculateB(a));
     }

     private MyRecord(double a, double b) {
          this(a, b, calculateC(a, b));
     }

     private static double calculateB(double a) {
          // Some calculations 
          return b;
     }

     private static double calculateC(double a, double b) {
          // Some calculations 
          return c;
     }
}

But using a factory method, like suggested by this answer might be preferable. The advantage is that factory method can have a clarifying name. E.g.

public static MyRecord fromA(double a) {
…

which even allows to have, e.g.

public static MyRecord fromB(double b) {
…

in the same class. Overloading constructors, on the other hand, requires more care. It should only be used when it is expected to be obvious to the users of the class which parameters have been omitted and in which way they will be generated from the given values.

Evan answered 15/3, 2022 at 9:15 Comment(0)
S
0

Using a static factory method is a solution but your API starts to get messy.

It's not possible to lessen the default record constructor visibility by design (https://mcmap.net/q/518357/-why-can-a-java-record-39-s-canonical-constructor-not-have-more-restrictive-access-than-the-record-level) so using an external factory class would also be messy.

In your case it would be best if you used a regular class to gain the option of having multiple independent constructors.

Sixfold answered 1/3, 2022 at 4:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.