Ahamed Abdul Rahman
Ahamed Abdul Rahman

Reputation: 526

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.

Upvotes: 1

Views: 478

Answers (2)

Jan Rieke
Jan Rieke

Reputation: 8142

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().

Upvotes: 1

GreyBeardedGeek
GreyBeardedGeek

Reputation: 30088

The problem is the way that you're invoking the Lombok builder.

Instead of

NumCheck numCheck = new NumCheck().toBuilder().maxCount(10).build();

you should use:

NumCheck numCheck = NumCheck.builder().maxCount(10).build();

The former creates an instance using the no-arg constructor, then creates a builder from that, and uses the builder to create a second instance, which is thrown away (not assigned to a variable).

The latter just creates one instance via the builder, and assigns it to the variable.

Upvotes: 0

Related Questions