• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Tim Cooke
  • Liutauras Vilda
  • Jeanne Boyarsky
  • paul wheaton
Sheriffs:
  • Ron McLeod
  • Devaka Cooray
  • Henry Wong
Saloon Keepers:
  • Tim Holloway
  • Stephan van Hulst
  • Carey Brown
  • Tim Moores
  • Mikalai Zaikin
Bartenders:
  • Frits Walraven

Java Record Builder Best Practice

 
Sheriff
Posts: 4638
582
VSCode Eclipse IDE TypeScript Redhat MicroProfile Quarkus Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I am new to Java Records and I'm not sure of the best practice for creating a builder for a Record.  Should the builder be part of the Record class as I have implemented below, or should it be in a separate class?

 
Sheriff
Posts: 28321
95
Eclipse IDE Firefox Browser MySQL Database
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
It looks to me like the standard procedure for the Builder pattern in Java is to have the Builder be an inner class of the class which it's responsible for building. So that's +1 in favour of doing that.

On the other hand many of the examples I've seen make the class being built by the Builder have a private constructor, so that the programmer is compelled to use the Builder to create an object of the class. This doesn't work with Records because they can't have private constructors. (Not that I know of, I didn't write code to try that out.)

However it looks to me like there are two reasons for Builders. One is to validate the input parameters, and the other is to provide a way to give names to the parameters instead of requiring the programmer to know which order the six or twelve parameters go in.

Validating the input parameters is covered by using a "custom constructor" in the record declaration. In your code it would look something like this:

So that should take care of the validation part. That leaves the Builder to provide the fluid interface, which looks like it can't be made compulsory but maybe that isn't as much of a problem. Inner class or not? Maybe inner class because that's the usual way to implement a Builder?
 
Ron McLeod
Sheriff
Posts: 4638
582
VSCode Eclipse IDE TypeScript Redhat MicroProfile Quarkus Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thanks for the feedback.  Yeah - It seems like builders are normally done with an inner class, but I wondered if things might be done differently for Records.

Another reason for using a builder is to be able to set values one-by-one from lambdas without having to use something like AtomicReference<String> with its set and get methods.

For example - parsing YAML file contents using switch with lambda-style case clauses using a builder:
Using AtomicReference:
 
Marshal
Posts: 79932
396
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Paul Clapham wrote:. . . Records . . . can't have private constructors. . . .

I tried that last week and got a compiler error. A record should have a constructor with the same access as the class or more permissive access. So you can only have a private constructor in a private record class and that means a private nested class. JLS link.
 
Ron McLeod
Sheriff
Posts: 4638
582
VSCode Eclipse IDE TypeScript Redhat MicroProfile Quarkus Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Paul Clapham wrote:



I don't think that is possible with Records -- I found that the first statement in the constructor must be a call to the default constructor using the this keyword (I'm new to this so maybe I'm missing something).

The best I could do was:
 
Saloon Keeper
Posts: 15725
368
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
There is no way you can get around providing a record type with a canonical constructor that is at least as visible as the type itself.

That means that you can't force clients of the record type to use a builder to construct instances of the record type.

That doesn't mean that the builder pattern is useless. You can still provide a record type with a builder class to make it easier to construct certain complex records. And yes, I would make the builder class a nested class of the record type.

Note that the term "inner class" explicitly refers to non-static nested classes. Say "nested class" instead.

Honestly, I'm a little disappointed with records in Java. I find that in 90% of the cases where I start writing a record, I have to change it to a regular class later because I can't easily enforce certain invariants. This is because I can't make the canonical constructor private.

Anyway, this is how I'd write your record type:

Some differences from your version:

  • I used a compact constructor declaration instead of a regular constructor.
  • I don't like to expose the pattern as a public field. Instead, I provide isValidCode() and isValidReason() methods.
  • I don't see any reason to convert the code to an integer. Are you going to perform arithmetic on it?
  • I usually disallow string values that start or end with whitespace. This also covers the case of a blank string.
  • If a field has no valid default value, I prefer to pass an initial value to the Builder constructor.
  •  
    Stephan van Hulst
    Saloon Keeper
    Posts: 15725
    368
    • Likes 2
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Another note on compact constructors. The compiler expands a compact constructor into something like this:

    That means that inside the compact constructor, you can validate and modify the constructor parameters, and you don't need to assign them to the record fields explicitly. Also keep in mind that inside the compact constructor body, code refers to the constructor parameter, while this.code refers to the record field.
     
    If you look closely at this tiny ad, you will see five bicycles and a naked woman:
    Gift giving made easy with the permaculture playing cards
    https://coderanch.com/t/777758/Gift-giving-easy-permaculture-playing
    reply
      Bookmark Topic Watch Topic
    • New Topic