Re: Records — Dealing with nullable values

Brian Goetz brian.goetz at oracle.com
Sat Dec 14 22:37:54 UTC 2019


Records are _transparent, shallowly immutable containers for their 
state_.  So either the record holds a Gender and exposes that as the 
API, or it holds an Optional<Gender>, and exposes that.  If you are 
using what is essentially a macro generator (like Lombok), I'm sure it 
has a hundred knobs you can twiddle to represent your favorite 
patterns.  But that's not what records are -- records are _nominal 
tuples_ (just as functional interfaces are _nominal function types_.)

I get why you are seeking a nulls-in, Optional-out protocol -- you're 
trying to follow Postel's law.  But if you really want to treat "no 
gender" as a valid point in the domain, then just make a record with an 
Optional<Gender> component, and push the `Optional.ofNullable()` to the 
caller (or, provide an overloaded constructor that does it for them.)  
But I would still use this pattern sparingly; the majority of uses I see 
of Optional in domain models are gratuitous.

On 12/14/2019 5:31 PM, Arash Shahkar wrote:
> In my example, gender can be left undefined, but not name. Dropping 
> requireNonNull(gender) from the compact constructor works, but leaves 
> the accessor for gender returning null (possibly).
>
> What I’d like to achieve is to have the accessor for gender return 
> Optional<Gender>, to clearly communicate that it might be null. I have 
> found this pretty common, and using this pattern has helped a lot.
>
> I was wondering if my “workaround” to be able to use records and drop 
> Lombok for these cases is a big no-no.
> On Dec 14, 2019, 5:16 PM -0500, Brian Goetz <brian.goetz at oracle.com>, 
> wrote:
>> No, that would not be a good idea.  If you find yourself using Optional
>> for a method parameter, or a field type, or a record component type,
>> you've almost certainly gone Optional-crazy.  It's a common, but
>> curable, disease.
>>
>> What you want to do is this:
>>
>>     record Foo(String name, Gender gender) {
>>         Foo {
>>             requireNonNull(name);
>>             requireNonNull(gender);
>>         }
>>     }
>>
>> The `Foo { ... }` is a _compact constructor_, which is a constructor
>> whose signature is the same as that of the record, and which
>> automatically handles the `this.x = x` boilerplate for you. This is
>> where you put argument validation / normalization.
>>
>>
>> On 12/14/2019 5:01 PM, Arash Shahkar wrote:
>>> Hi,
>>>
>>> I’ve been using Lombok for quite a while now to automatically 
>>> generate boilerplate code for "data carrier" classes. However, a 
>>> pattern I really like and have found to work very well is to verify 
>>> non-nullity of all the fields that must not be null, and return an 
>>> Optional<Field> from the accessor of a field if it’s allowed to have 
>>> a null value. Consider:
>>>
>>> @lombok.Value
>>> class Person {
>>>
>>>     String name;
>>>     Gender gender;
>>>
>>>     Person(String name, Gender gender) { … } // customize the 
>>> constructor, verifying that name is defined and valid
>>>
>>>     Optional<Gender> getGender() { // customize the accessor for 
>>> gender, to make sure calling code deals with the possibility of 
>>> gender being null
>>>         return ofNullable(gender);
>>>     }
>>> }
>>>
>>> Doing a similar thing with records is not possible, due to the fact 
>>> that the accessor for gender has to exactly return a Gender. With 
>>> the current specification for records, the only option is to do:
>>>
>>> record Person(String name, Optional<Gender> gender) {
>>>     …
>>> }
>>>
>>> Is this considered good practice? Do you suggest any alternatives?
>>



More information about the amber-dev mailing list