I am initializing a class via Lombok Builder. Through which I am initializing a variable. But that’s variable’s value is not available when I use that in a Predicate definition.
My code looks this:
@Builder(toBuilder = true) @NoArgsConstructor @AllArgsConstructor class NumCheck { int maxCount; Predicate<Integer> isLessThanMax = num -> maxCount < num; } public class PredicateInstance { public static void main(String[] args) { int a = 5, b=11; NumCheck numCheck = new NumCheck().toBuilder().maxCount(10).build(); Stream.of(2,5,6,7,10,11,20,32).filter(numCheck.isLessThanMax).forEach(System.out::println); } }
While debugging, the value of maxCount was not getting initialized to 10. It remained 0.
If I remove the Lombok builders and do it in normal way, this is working fine:
class NumCheck { int maxCount; Predicate<Integer> isLessThanMax = num -> maxCount < num; } public class PredicateInstance { public static void main(String[] args) { int a = 5, b=11; NumCheck numCheck = new NumCheck(); numCheck.maxCount = 10; Stream.of(2,5,6,7,10,11,20,32).filter(numCheck.isLessThanMax).forEach(System.out::println); } }
Is it a known limitation of Lombok or am I doing anything wrong here?
Workarounds that worked but not applicable for my scenario:
- Declaring the maxCount as static.
- Declare BiPredicate and send maxCount along with num parameter.
Advertisement
Answer
You should ask yourself whether the isLessThanMax
should really be settable via the builder. It seems to me more like a constant definition. If that is the case, you should make it final
. Lombok ignores final
fields with an initializer when generating the builder.
(You also don’t need @Builder.Default
in this case. However, there is nothing wrong with @Builder.Default
on fields. There is exactly zero performance or memory impact.)
If you are interested why your code behaves so strange, here’s the explanation.
When instantiating using the no-args constructor, isLessThanMax
is initialized using a reference to the field maxCount
of this instance. maxCount
is defaulted to 0
. Then you call toBuilder()
, which simply copies all values of the instance to the builder. Finally you call maxCount(10).build()
on that builder. The resulting second instance will have maxCount = 10
, but exactly the same isLessThanMax
value than the first instance. That means that the predicate of the second instance still references the field of the first instance, which is 0
. So even though you set maxCount = 10
for the second instance, the Predicate
still compares using the maxCount = 0
value from the first instance.
tl;dr: Don’t use method references that reference instance fields in combination with toBuilder()
.