Explain @ Builder usage in Lombok

Posted by morpheuz on Thu, 04 Nov 2021 13:26:05 +0100

<p>< strong > reprint source: < / strong > < a href=“ https://www.cnblogs.com/ajing2018/p/14281700.html ">
https://www.cnblogs.com/ajing2018/p/14281700.html</a></p>

Builder uses creator mode, also known as builder mode. In short, it is to create an object step by step. It shields the details of the construction to the user, but it can finely control the construction process of the object.

 

Basic use

@The builder annotation generates a relatively complex builder API for your class@ Builder allows you to call your code as shown below to initialize your instance object:

Student.builder()
               .sno( "001" )
               .sname( "admin" )
               .sage( 18 )
               .sphone( "110" )
               .build();

@Builder can be placed on classes, constructors or methods. Although the two patterns on classes and constructors are the most common use cases, @ builder is the easiest to explain with the use cases on methods.

 

So what did @ Builder do internally for us?

  1. Create an internal static class called ThisClassBuilder with the same properties as entity class (called builder).
  2. In the Builder: for all attributes and uninitialized final fields in the target class, corresponding attributes will be created in the builder.
  3. In the Builder: create a parameterless default constructor.
  4. In the Builder: for each parameter in the entity class, a setter like method will be created, but the method name is the same as the parameter name. And the return value is the builder itself (easy to chain call), as shown in the above example.
  5. In the Builder: a build() method. When this method is called, the entity object will be created according to the set value.
  6. In the Builder: a toString() method is also generated.
  7. In the entity class: a builder() method is created, which is used to create the builder.

With so much to say, let's understand it through the following example

 
@Builder
public class User {
    private final Integer code = 200;
    private String username;
    private String password;
}
 
// After compilation:
public class User {
    private String username;
    private String password;
    User(String username, String password) {
        this.username = username; this.password = password;
    }
    public static User.UserBuilder builder() {
        return new User.UserBuilder();
    }
 
    public static class UserBuilder {
        private String username;
        private String password;
        UserBuilder() {}
 
        public User.UserBuilder username(String username) {
            this.username = username;
            return this;
        }
        public User.UserBuilder password(String password) {
            this.password = password;
            return this;
        }
        public User build() {
            return new User(this.username, this.password);
        }
        public String toString() {
            return "User.UserBuilder(username=" + this.username + ", password=" + this.password + ")";
        }
    }
}
 

Combined usage

1. Use @ Singular annotation set in @ builder

@Builder can also generate a special method for parameters or fields of collection type. It modifies an element in the list instead of the whole list. It can add an element or delete an element.

Student.builder()
                .sno( "001" )
                .sname( "admin" )
                .sage( 18 )
                .sphone( "110" ).sphone( "112" )
                .build();

  This makes it easy to include 2 strings in the list < string > field. But to manipulate a collection like this, you need to use @ Singular to annotate fields or parameters.  

When annotating a collection field with @ single annotation (using @ Builder annotation class), lombok will treat the Builder node as a collection and generate two adder methods instead of setter methods.

  • Add a single element to a collection
  • One adds all elements of another collection to the collection

Setters that set only the collection (replacing anything added) will not be generated. The clear method is also generated. These singular builders are relatively complex, mainly to ensure the following features:

  1. When build() is called, the generated collection will be immutable.
  2. After calling build(), calling one of the adder methods or the clear method will not modify any generated object. If you call build() after modifying the collection, an object entity created based on the previous object will be created.
  3. The generated set will be compressed to the smallest feasible format while maintaining efficiency.

@Singular can only be applied to collection types known to lombok. Currently, the supported types are:

java.util: 

  • Iterable,   Collection, and List   (generally, it is supported by compressed and unmodifiable ArrayList)
  • Set,   SortedSet, and   NavigableSet   (generally, a variable size and unmodifiable HashSet or TreeSet is generated)
  • Map,   SortedMap, and   NavigableMap   (generally, a variable size and unmodifiable HashMap or treemap is generated)

Guava's com.google.common.collect:

  • ImmutableCollection and ImmutableList
  • ImmutableSet and ImmutableSortedSet
  • ImmutableMap, ImmutableBiMap, and ImmutableSortedMap
  • ImmutableTable

  Let's take a look at the compilation after using @ Singular annotation:

 
@Builder
public class User {
    private final Integer id;
    private final String zipCode = "123456";
    private String username;
    private String password;
    @Singular
    private List<String> hobbies;
}
 
// After compilation:
public class User {
    private final Integer id;
    private final String zipCode = "123456";
    private String username;
    private String password;
    private List<String> hobbies;
    User(Integer id, String username, String password, List<String> hobbies) {
        this.id = id; this.username = username;
        this.password = password; this.hobbies = hobbies;
    }
 
    public static User.UserBuilder builder() {return new User.UserBuilder();}
 
    public static class UserBuilder {
        private Integer id;
        private String username;
        private String password;
        private ArrayList<String> hobbies;
        UserBuilder() {}
        public User.UserBuilder id(Integer id) { this.id = id; return this; }
        public User.UserBuilder username(String username) { this.username = username; return this; }
        public User.UserBuilder password(String password) { this.password = password; return this; }
 
        public User.UserBuilder hobby(String hobby) {
            if (this.hobbies == null) {
                this.hobbies = new ArrayList();
            }
            this.hobbies.add(hobby);
            return this;
        }
 
        public User.UserBuilder hobbies(Collection<? extends String> hobbies) {
            if (this.hobbies == null) {
                this.hobbies = new ArrayList();
            }
            this.hobbies.addAll(hobbies);
            return this;
        }
 
        public User.UserBuilder clearHobbies() {
            if (this.hobbies != null) {
                this.hobbies.clear();
            }
            return this;
        }
 
        public User build() {
            List hobbies;
            switch(this.hobbies == null ? 0 : this.hobbies.size()) {
            case 0:
                hobbies = Collections.emptyList();
                break;
            case 1:
                hobbies = Collections.singletonList(this.hobbies.get(0));
                break;
            default:
                hobbies = Collections.unmodifiableList(new ArrayList(this.hobbies));
            }
            return new User(this.id, this.username, this.password, hobbies);
        }
        public String toString() {
            return "User.UserBuilder(id=" + this.id + ", username=" + this.username + ", password=" + this.password + ", hobbies=" + this.hobbies + ")";
        }
    }
}
 

In fact, the creator of lombok is very attentive. When building () to create an instance object,
Instead of directly using Collections.unmodifiableList(Collection) to the bedstead instance, there are three cases.

  • First, create an empty list when there are no elements in the collection
  • In the second case, when there is an element in the collection, an immutable single element list is created
  • In the third case, create a list of appropriate size according to the number of elements in the current collection

  Of course, we look at the code generated by compilation and create three methods for collection operation:

  • hobby(String hobby): adds an element to the collection
  • Hobbies (collection <? Extensions string > hobbies): add all elements of a collection
  • clearHobbies(): clear the current collection data

2. The @ singular annotation configures the value attribute

Let's first look at the details of @ Singular annotation:

@Target({FIELD, PARAMETER})
@Retention(SOURCE)
public @interface Singular {
    // Modify the method name for adding collection elements
    String value() default "";
}
  • Test how to use the annotation attribute value
 
@Builder
public class User {
    private final Integer id;
    private final String zipCode = "123456";
    private String username;
    private String password;
    @Singular(value = "testHobbies")
    private List<String> hobbies;
}
 
// Test class
public class BuilderTest {
    public static void main(String[] args) {
        User user = User.builder()
                .testHobbies("reading")
                .testHobbies("eat")
                .id(1)
                .password("admin")
                .username("admin")
                .build();
        System.out.println(user);
    }
}
 

Note that when we use the annotation attribute value, the method name changes accordingly when we add collection elements. But has the name of the method generated at the same time to add the entire collection changed? Let's look at the compiled code:

 
/ After compilation:
public class User {
    // Omit some of the code and only look at the key parts
    public static class UserBuilder {
        public User.UserBuilder testHobbies(String testHobbies) {
            if (this.hobbies == null) {
                this.hobbies = new ArrayList();
            }
            this.hobbies.add(testHobbies);
            return this;
        }
 
        public User.UserBuilder hobbies(Collection<? extends String> hobbies) {
            if (this.hobbies == null) {
                this.hobbies = new ArrayList();
            }
            this.hobbies.addAll(hobbies);
            return this;
        }
        
        public User.UserBuilder clearHobbies() {
            if (this.hobbies != null) {
                this.hobbies.clear();
            }
            return this;
        }
    }
}
 

  As you can see, only the method name of adding an element has changed.

3. Use of @ builder.default

For example, there is an entity class:

 
@Builder
@ToString
public class User {
    @Builder.Default
    private final String id = UUID.randomUUID().toString();
    private String username;
    private String password;
    @Builder.Default
    private long insertTime = System.currentTimeMillis();
}
 

In the class, I add the annotation @ Builder.Default on both id and insertTime. When I use this entity object, I don't need to initialize the values for these two fields, as follows:

 
public class BuilderTest {
    public static void main(String[] args) {
        User user = User.builder()
                .password("admin")
                .username("admin")
                .build();
        System.out.println(user);
    }
}
 
// Output content:
User(id=416219e1-bc64-43fd-b2c3-9f8dc109c2e8, username=admin, password=admin, insertTime=1546869309868)
 

lombok initializes these two field values for us when instantiating the object.

Of course, if you set values for these two fields again, the default defined values will be overwritten, as follows:

 
public class BuilderTest {
    public static void main(String[] args) {
        User user = User.builder()
                .id("admin")
                .password("admin")
                .username("admin")
                .build();
        System.out.println(user);
    }
}
// Output content
User(id=admin, username=admin, password=admin, insertTime=1546869642151)
 

4. @Builder detailed configuration

Let's take a closer look at the detailed implementation of @ Builder annotation class:

 
@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(SOURCE)
public @interface Builder {
    // If@Builder Annotations on classes can be used @Builder.Default Specifies the initialization expression
    @Target(FIELD)
    @Retention(SOURCE)
    public @interface Default {}
    // Create in the specified entity class Builder The name of the method. The default is: builder (Personally, I don't think it's necessary to modify it)
    String builderMethodName() default "builder";
    // appoint Builder The name of the method used in the component entity class. The default is: build (Personally, I don't think it's necessary to modify it)
    String buildMethodName() default "build";
    // Specifies the name of the creator class created. The default is: entity class name+Builder
    String builderClassName() default "";
    // use toBuilder You can continue to create an object based on an instance. (that is, reuse the value of the original object)
    boolean toBuilder() default false;
    
    @Target({FIELD, PARAMETER})
    @Retention(SOURCE)
    public @interface ObtainVia {
        // tell lombok Get value using expression
        String field() default "";
        // tell lombok Get value using expression
        String method() default "";
 
        boolean isStatic() default false;
    }
}
 

For the above annotation attribute, I only test a commonly used toBuilder, because when we operate on entity objects, we often assign a secondary value to a field of some entity objects. This attribute will be used at this time. However, this will create a new object instead of the original object. The original object properties are immutable unless you want to add @ Data or @ setter methods to the entity class. Let's test:

 
@Builder(toBuilder = true)
@ToString
public class User {
    private String username;
    private String password;
}
// Test class
public class BuilderTest {
    public static void main(String[] args) {
        User user1 = User.builder()
                .password("admin")
                .username("admin")
                .build();
        System.out.println(user1);
 
        User user2 = user1.toBuilder().username("admin2").build();
        // verification user2 Is it based on user1 Created by an existing property of
        System.out.println(user2);
        // Verify that the object is the same object
        System.out.println(user1 == user2);
    }
}
// Output content
User(username=admin, password=admin)
User(username=admin2, password=admin)
false
 

5. @Builder global configuration

# Disable @ Builder
lombok.builder.flagUsage = [warning | error] (default: not set)
# Use Guaua
lombok.singular.useGuava = [true | false] (default: false)
# Whether to use singular automatically. It is used by default
lombok.singular.auto = [true | false] (default: true)
  • In general, @ Builder is still very easy to use.

 

Topics: Lombok