Reputation:
In my code I have a class that looks like the following
public enum Test {
VALUE1,
VALUE2
}
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class MyClass {
@JsonProperty(name = "test")
private Optional<Test> test = Optional.empty();
}
This works fine, but it gives the following error:
warning: @Builder will ignore the initializing expression entirely.
Great, let me add @Builder.Default
...
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class MyClass {
@Builder.Default
@JsonProperty(name = "test", required = true)
private Optional<Test> test = Optional.empty();
}
This fixes the error, but now the test
value is forced null when it doesn't exist instead of Optional.empty
. I'd like to keep the Optional pattern and let the user of the object decide how they want to handle the Optional, not Lombok or Jackson.
Is there a way to make this work so the default remains Optional.empty()
? If not, is there a way I can ignore this warning since it can't be fixed?
EDIT:
Imagine we pass a payload to some endpoint that is empty (because we want to test the optional of the only value in the object)
POST /my/end/point
and the payload would be {}
We then use Jackson to get the object back
MyClass result = mapper.readValue(payload, MyClass.class);
When we look at the resulting object, we will find that MyClass.test
is null
rather than Optional.empty()
as expected when @Builder.Default
is used. Otherwise, we get the warning above, but the default value is correctly specified as Optional.empty()
.
Upvotes: 2
Views: 12269
Reputation: 21519
Since you have a builder, I’d suggest switching to @Value
, making your class immutable. You can use the toBuilder
option if you want to make further changes after building the object.
As for the argument that Optional setters are “ugly”, I think they miss the point of Optional. If you are using Optionals holistically, then the production code that is doing the setting will already have an optional. Requiring it for the setter or builder argument means the caller has to make a determination about whether the argument could be null or not. This is a good thing! The caller will have to consider and deal with possible nulls, perhaps refactoring so that the layer above has to also provide an optional, and so on.
In practice, nullable values most often come from a deserialisation framework like Jackson, which can do the wrapping for you. That means you don’t actually have to deal with the “ugly” very often.
Never skip on something that can remove bugs from your code just because someone says it is ugly. Code with fewer bugs is beautiful.
Upvotes: 0
Reputation: 37916
There's a code style issue. Letting the Lombok to generate getter/setter/constructor/builder for an optional field results in this:
public MyClass(Optional<Test> test) {
this.test = test;
}
public Optional<Test> getTest() {
return test;
}
public void setTest(Optional<Test> test) {
this.test = test;
}
// ... imagine Builder here ...
The getter is ok, but the constructor, the setter, and the Builder become very inconvenient to use. You have to wrap a Test
using one of the static methods on Optional
like this:
// If value is known
myClass.setTest(Optional.of(Test.VALUE1));
// If initializing with a variable which maybe null
myClass.setTest(Optional.ofNullable(anotherTest));
It's ugly. There is a better way.
I assume that you only want the Optional
for the return type on the getter, so provide your own getter implementation, forcing Lombok to not generate it’s own.
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class MyClass {
@JsonProperty(name = "test", required = true)
private Test test;
public Optional<Test> getTest() {
return Optional.ofNullable(test);
}
}
Source of Wisdom: Optionals and Lombok @ Medium
Upvotes: 5