Roel and I have just been working on a design for @Builder, and it turns out that the earlier design I submitted is not quite 'complete' (did not cover all possible corner cases), and does not result in sensible stuff in various fairly common cases.
So, we've come up with a slight tweak on the concept. Our current intention is to develop this specification into a lombok feature over the next month, release an 0.11.10 with it, and then start working on an 1.0.0 release, which probably involves having a think about renaming @EqAHC and using @Exclude annotations instead of the stringly typed exclude="" parameter, because we should probably sort out that sort of breaking change before we do that.
@Builder can be put on either a constructor/static method, or on a class.
If put on a constructor or static method, @Builder operates like this:
(NB: Consider a constructor as being equal to a static method with the return type of its class, including generics)
An inner class named XBuilder (X = the return type of the static method annotated) is generated.
Inside it, 1 method for each parameter of the annotated static method is generated, with
the name of the parameter as name of the method, 1 parameter (type equal to the parameter's type), and a return type
of XBuilder itself. It also has a build() method, which returns the same type as the static method. This build() method
will invoke your static method with all parameters. The default value for each parameter is the java 'null' value, so,
null / 0 / 0.0 / false / '\0'. There is no way to tell the difference between an explicitly set 'null' value, and no
value set at all. Also, the builder has the same type parameters as the static method (constructors have the same
type parameters as their owning class). In addition, a sibling static method will be generated with no arguments which
returns an instance of the builder class. By default, this method will by default be called 'builder', but it is configurable via:
@Builder(methodName = "myOwnName"). The build() method copies all listed exceptions from the annotated static method.
Builder classes have a sane toString, but no getters, no equals, and no hashCode. It is legal for the builder class
to already exist; in this case, all methods that would be generated by lombok are generated inside this class, unless
already present. We only check the name of the method and the parameter count. Builder classes also get a no-args
package private constructor. If the builder type already exists and it contains 1 or more constructors already, no
constructor is generated, but the no-args constructor will still be called. Your compiler will error if this does not exist.
@Builder on a class is also valid: In that case, you get an implied @AllArgsConstructor(access=PRIVATE), and it is as
if you put the @Builder annotation on this generated @AllArgsConstructor. If other lombok annotations are also present on
this type that carry their own implicit constructor annotations (such as @Data which implies @RequiredArgsConstructor, and
@Value which implies @AllArgsConstructor), then @Builder's implied constructor 'wins' and the other implied constructor
annotations will be ignored.
If the implicit @AllArgsConstructor will not be generated, then @Builder will still call what this constructor would have looked like,
whether it is present or not. That means, either you explicitly write this constructor yourself, or, your compiler will produce an error.
There are 2 ways when no constructor will be generated by an implicit @AllArgsConstructor:
* There is an actual explicitly written out constructor
* There is an explicit @XArgsConstructor annotation on the type. It may carry AccessLevel.NONE.
Not in this design (but later versions may start supporting some of this; note the above proposal can be expanded to support almost all
of the things below without breaking backwards compatibility):
* Special casing collections (add/put methods and presumably no 'set' method).
* varargs. Putting @Builder on a varargs method/constructor is not allowed.
* Rebuilder or any other way to initialize the builder with the same values as an existing instance.
* Default values (other than null / 0.0 / etc)
* null checks in the builder's generated 'setters'.
* Any other validation in the builder's generated 'setters'.
* choosing a different name for either the builder type or the build() method inside the builder type.
* Getters, equals, or hashCode in the builder. Don't use builder instances as map keys!
* For now no guarantee that @Getter, @EqualsAndHashCode, or other lombok type-level annotations will actually do anything on
an already existing builder type.
* Support for putting @Builder on a non-static method as a way to 'fluentify' the API of a method with a huge parameter list.
We probably will support that at some point, but @Builder as a name is misleading, so it'll be a differently named annotation
that operates very similarly to @Builder.
* Support for arguments on the builder() itself (sometimes used for mandatory arguments). If we would support this, we'd look
at the constructor that already exists in the builder type and copy its argument list, but this can get a little complex with
generics. We may also support this via @Builder.Mandatory or some such on the mandatory fields, but in general this does not
seem like it'll happen, because the point of a builder is to create a fluent API, after all.