Home > Blockchain >  With lombok, can required-arg constructor coexist with @Value, @Builder and @Builder.Default annotat
With lombok, can required-arg constructor coexist with @Value, @Builder and @Builder.Default annotat

Time:12-25

Using lombok (1.18.24), is it possible to generate (1) a constructor that accepts args only for non-initialized members and also (2) a builder? I've tried a variety of annotation sets:

  1. []
@Value @Builder class Foo {
    String alpha;
    @Builder.Default String bravo = "x";
    Foo(String alpha) {this.alpha = alpha;}
    static void bar() {new Foo("y");}
}
[ERROR] Foo.java:[6,8] constructor Foo in class Foo cannot be applied to given types;
[ERROR]   required: java.lang.String
[ERROR]   found:    java.lang.String,java.lang.String
[ERROR]   reason: actual and formal argument lists differ in length
  1. [AllArgsConstructor]
@Value @Builder @AllArgsConstructor class Foo {
    String alpha;
    @Builder.Default String bravo = "x";
    Foo(String alpha) {this.alpha = alpha;}
    static void bar() {new Foo("y");}
}
[ERROR] Foo.java:[11,47] variable bravo might not have been initialized
  1. [RequiredArgsConstructor]
@Value @Builder @RequiredArgsConstructor class Foo {
    String alpha;
    @Builder.Default String bravo = "x";
    static void bar() {new Foo("y");}
}
[ERROR] Foo.java:[10,29] constructor Foo in class Foo cannot be applied to given types;
[ERROR]   required: java.lang.String,java.lang.String
[ERROR]   found:    java.lang.String
[ERROR]   reason: actual and formal argument lists differ in length
  1. [AllArgsConstructor, RequiredArgsConstructor]
@Value @Builder @AllArgsConstructor @RequiredArgsConstructor class Foo {
    String alpha;
    @Builder.Default String bravo = "x";
    static void bar() {new Foo("y");}
}
[ERROR] constructor Foo(java.lang.String,java.lang.String) is already defined in class Foo
[ERROR] Foo.java:[8,37] constructor Foo(java.lang.String,java.lang.String) is already defined in class Foo

CodePudding user response:

  1. Lombok makes an all-args constructor for you unless you write a constructor yourself, in that case, you have to add @AllArgsConstructor. Which you should do here. You may make it private if you like (@AllArgsConstructor(access = AccessLevel.PRIVATE). It needs to exist for lombok's builder to make an object. It needs to include the defaulted stuff (that's just a default, so, the builder needs to be capable of passing a non-default value, too).

  2. The problem is in your own code - you wrote your own constructor which doesn't set the bravo field. In general it's a bad idea to also have your own constructor, especially if said constructor does not call the lombok-generated one. The @Builder.Default modifies what builder does (namely: It will use that "X" value if you ask a builder to build() when no value for bravo has ever been set for it). It removes that "X" as it goes, and @Value has made that field final of course - so, your constructor fails to set an uninitialized (because builder 'takes away' the "X") final field, which is a compiler error.

  3. Same problem.

  4. AllArgs and RequiredArgs are the same constructor, so lombok tries to generate them both, which doesn't work.

@Value @Builder @AllArgsConstructor class Foo {
    String alpha;
    @Builder.Default String bravo = "x";
    Foo(String alpha) {this(alpha, "x");}
    static void bar() {new Foo("y");}
}

Is what you're looking for. Or, more likely, get rid of that constructor, and write:

@Value @Builder @AllArgsConstructor class Foo {
    String alpha;
    @Builder.Default String bravo = "x";
    static void bar() {return builder().alpha("y").build(); }
}

It's generally not a good idea to hybridize anything in programming - and you're hybridizing your approach to construction here. Some custom constructor for some, builders for others. If you must, okay, but, it's a cost. One of the costs is confusion (that seems to have happened here). Another is bug-prone code: bugs that cannot be easily found (because one person always uses one way and therefore a bug in the other way isn't in any of their code paths, but it is in yours). A third is style debates; if there are 2 ways to do the same thing, either code arbitrarily picks a way (that's bad; causes code to be needlessly difficult to read by doing the same thing in different ways, which incorrectly insinuates there is a meaningful difference the reader will either be worrying about, or will be spending some time on to chase down that, no, in fact, it doesn't matter - or, you need a style guide to dictate when to use which way. And then you need to enforce that style guide which is very hard to do; most folks don't, as a rule, want to spend time writing a custom lint rule).

NB: I said 'generally not a good idea' - not 'it is always wrong'. There are good reasons to hybridize but be aware that it is a cost to introduce it. Hence, the onus of making an argument is on the author of the hybridized approach. They need to explain that despite the significant costs introduced by having multiple ways to do the same thing, that it's nevertheless worthwhile here. Your code is clearly a simplified example for the sake of asking a streamlined SO question (excellent!), so perhaps there is a good reason here. However, in the likely scenario that there is not - get rid of that hand-written constructor.

  • Related