Search code examples
javajava-record

Java records and default/derived finalized properties


I have this:

// the id-constructor-parameter should not exist 
// as the value should always be calculated within the constructor.
public record User(String id, String name) {

    public User {
        id = UUID.randomUUID();
    }

    public User(String name) {
        this(null, name);

    }
}

How can I remove the parameter id from the constructor of a java record?

The id will be always reassigned/computed within the constructor, so it should not be passed by the constructor (i.e. id is a generated/derived property). Something like this, unfortunately, does not work:

public record User(String name) {
    // Unfortunately, this doesn't work in Java record
    private final String id;
    public User {
        id = UUID.randomUUID();
    }
}

Solution

  • The semantic principle you want fundamentally means you don't actually have a record.

    Records are a little more than just 'a class where I want java to take care of equals and hashCode and getter methods for me'. For example, records also get an automatic deconstructor, and will therefore automatically support with-blocks, but, and herein lies the key issue, that fundamentally doesn't work with your take on how such a class works.

    Hence, not a record.

    Now, deconstructors and with-blocks aren't in java. Yet. They are in advanced planning stages and will probably be in something along the lines of java 23 or so.

    Deconstructor

    A deconstructor does what you'd think: A constructor takes in a value for each field (well, parameter, but there's the notion, at least with records, that each parameter is just the value for each of the fields) and turns that into an object. A deconstructor does the reverse, taking in an instance of User and turning that into its constituent parts. It's not like a normal method, because normal methods can only return one thing, whereas a deconstructor returns a series of things (here, a UUID and a name).

    with-blocks

    with blocks is one of a few features (the primary other one being pattern matching) that requires deconstructors to be a thing in order to 'work'. A with-block looks like this:

    User u = new User("Joe");
    ...
    
    public User rename(User in, String newName) {
      return u with {
        name = newName;
      };
    }
    

    The syntax isn't important (not fleshed out in the proposals yet, this is normal, syntax is generally not all that interesting and the last thing to be figured out), the point is: Inside 'with', the object (u, here) is deconstructed, i.e. inside the braces all the fields (here, UUID id and String name) exist as local variables you can write to, and at the end of the block, those locals are used to reconstitute a new instance, which is what the whole expression resolves to.

    For all this to work, it has to be possible for the java compiler to know how to go through the process of breaking an instance of User into its parts and then recreating a new user object based on those parts later, and that requires that it is possible to create a user with a provided UUID instead of having your system random up a new id for them.