Skip to content
Advertisement

Predicates of a class are not able to use the instance variable if the class is initialised via Lombok

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.

enter image description here
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:

  1. Declaring the maxCount as static.
  2. 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().

User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement