In this tutorial, we explore the possibilities of using Lombok’s @Builder annotation generation method Builder to improve usability.

An overview of the

In this tutorial, we’ll explore the possibility of using Lombok’s methods to generate a method Builder. The @Builder annotation is intended to improve usability by providing a flexible way to call a given method, even if it has many parameters.

@Builder about simple methods

How to use methods flexibly is a common topic, and powers accept multiple inputs. See the following example:

void method(@NotNull String firstParam, @NotNull String secondParam, 
            String thirdParam, String fourthParam, 
            Long fifthParam, @NotNull Object sixthParam) {
    ...            
}
Copy the code

If an argument not marked as NOTNULL is optional, the method may accept all of the following calls:

method("A", "B", null, null, null, new Object()); method("A", "B", "C", null, 2L, "D"); method("A", "B", null, null, 3L, this); .Copy the code

This example already shows some problem points, such as:

  • The caller should know which argument is which (for example, in order to change the first call to provideLongThe caller also needs to knowLongWill be the fifth parameter).
  • The inputs must be set in the given order.
  • The name of the input parameter is opaque.

Also, from the provider’s point of view, methods that provide fewer arguments would mean a lot of overloading of method names, such as:

void method(@NotNull String firstParam, @NotNull String secondParam, @NotNull Object sixthParam); void method(@NotNull String firstParam, @NotNull String secondParam, String thirdParam, @NotNull Object sixthParam); void method(@NotNull String firstParam, @NotNull String secondParam, String thirdParam, String fourthParam, @NotNull Object sixthParam); void method(@NotNull String firstParam, @NotNull String secondParam, String thirdParam, String fourthParam, Long fifthParam, @NotNull Object sixthParam); .Copy the code

For better usability and to avoid boilerplate code, you can introduce a method builder. ProjectLombok already provides an annotation to make it easy to use the builder. The sample method above can be commented in the following way:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call")
void method(@NotNull String firstParam, @NotNull String secondParam, 
            String thirdParam, String fourthParam, 
            Long fifthParam, @NotNull Object sixthParam) {
    ...            
}
Copy the code

Therefore, the method is called as follows:

methodBuilder()
        .firstParam("A")
        .secondParam("B")
        .sixthParam(new Object())
        .call();

methodBuilder()
        .firstParam("A")
        .secondParam("B")
        .thirdParam("C")
        .fifthParam(2L)
        .sixthParam("D")
        .call();

methodBuilder()
        .firstParam("A")
        .secondParam("B")
        .fifthParam(3L)
        .sixthParam(this)
        .call();
Copy the code

** This way, method calls are easier to understand and change. Some comments:

  • By default, a builder method on a static method (the method that gets the builder instance) is itself a static method.
  • By default,call()The method will have the same throw signature as the original method.

The default value

In many cases, it is very helpful to define default values for input parameters. Unlike some other languages, Java has no language elements to support this requirement. So, in most cases, this is done through method overloading, structured as follows:

method() { method("Hello"); } method(String a) { method(a, "builder"); } method(String a, String b) { method(a, b, "world!" ); } method(String a, String b, String c) { ... acutal logic here ... }Copy the code

When you use the Lombok builder, a builder class is generated in the target class. This builder category:

  • Has the same number of properties and parameters as a method.
  • Its argument has a mastermind.

You can also define classes manually so that you can define default values for parameters. In this way, the above method is as follows:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder") method(String a, String b, String c) { ... acutal logic here ... } private class MethodBuilder { private String a = "Hello"; private String b = "builder"; private String c = "world!" ; }Copy the code

With this addition, if the caller does not specify parameters, the default values defined in the generator class are used.

Note: In this case, we do not have to declare all the input parameters of the methods in the class. If the method’s input parameter does not exist in the class, Lombok generates an additional attribute accordingly.

Typing method

It is usually necessary to define the return type of a given method with one of the inputs, for example:

public <T> T read(byte[] content, Class<T> type) {... }Copy the code

In this case, the builder class will also be a typed class, but the builder method will create an instance without bounded types. See the following example:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder") public <T> T read(byte[] content, Class<T> type) {... }Copy the code

In this case, the methodBuilder method will create a methodBuilder with no bounded type parameters. This causes the following code not to compile (this is Class

and is made by Class

):

methodBuilder()
    .content(new byte[]{})
    .type(String.class)
    .call();
Copy the code

Can solve this problem. Type and use it as:

methodBuilder()
    .content(new byte[]{})
    .type((Class)String.class)
    .call();
Copy the code

It will compile, but there is one other aspect that needs to be mentioned: in this case, the return type Call method will not be a string, but it will still be an unbound T. Therefore, the client must convert the return type as follows:

String result = (String)methodBuilder()
    .content(new byte[]{})
    .type((Class)String.class)
    .call();
Copy the code

This solution works, but it also requires the caller to transform both the input and the result. Since the original motivation was to provide a call-friendly way to invoke these methods, it is recommended to consider one of the following two options.

Rewrite Builder method

As mentioned above, the root of the problem is that the builder method creates instances of the builder class without specific type parameters. You can still define builder methods in the class and create instances of the builder class using the required types:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder")
public <T extends Collection> T read(final byte[] content, final Class<T> type) {...}

public <T extends Collection> MethodBuilder<T> methodBuilder(final Class<T> type) {
    return new MethodBuilder<T>().type(type);
}

public class MethodBuilder<T extends Collection> {
    private Class<T> type;
    public MethodBuilder<T> type(Class<T> type) { this.type = type; return this; }
    public T call() { return read(content, type); }
}
Copy the code

In this case, the caller does not have to cast at any time, and the call looks like this:

List result = methodBuilder(List.class)
    .content(new byte[]{})
    .call();
Copy the code

Cast in the setter

You can also cast builder instances in setters for type parameters:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder")
public <T extends Collection> T read(final byte[] content, final Class<T> type) {...}

public class MethodBuilder<T extends Collection> {
    private Class<T> type;
    public <L extends Collection> MethodBuilder<L> type(final Class<L> type) { 
        this.type = (Class)type; 
        return (MethodBuilder<L>) this;
    }
    public T call() { return read(content, type); }
}
Copy the code

conclusion

Using @Builder in a method brings the following advantages:

  • There is more flexibility in the callers
  • There is no default input value for method overloading
  • Improved readability of method calls
  • Allows similar calls from the same builder instance.

If you think my writing is good, you might as well do me a favor and give me a thumbs up, so that more people can see this article. The complete information has been packed up for you, you can click on itAccess to learning materials