Skip to content

@Builder

YellowStar5 edited this page Aug 10, 2019 · 3 revisions

@Builer

... and Bob's your uncle: No-hassle fancy-pants APIs for object creation!

@Builder 是在 lombok v0.12.0 中作为实验性功能引入的。

@Builder 获得 @Singular 支持,并从 lombok v1.16.0 升级到 lombok 主包。

@Singular@Builder 在 lombok v1.16.8 之后增加了一个 clear 的方法。

@Builder.Default 功能已在 lombok v1.16.16 中添加。

从 lombok v1.18.8 开始,@Builder(builderMethodName = "") 是合法的(并且将禁止生成builder方法)。

从 lombok v1.18.8 开始,@Builder(access = AccessLevel.PACKAGE) 是合法的(并将生成具有指示访问级别的构建器类,构建器方法等)。

概览

@Builder 注解为你的类生成复杂的builder API。

@Builder 允许你使用以下代码自动生成使你的类可实例化所需的代码: Person.builder().name("Adam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build();

@Builder 可以放在类,构造函数或方法上。虽然“在类上”和“在构造函数上”模式是最常见的用例,但@Builder最容易用“方法”用例来解释。

@Builder 注解的方法(从现在开始称为目标)会给你做以下7件事:

  • 一个名为 FooBuilder 的内部静态类,与静态方法(称为builder)具有相同的类型参数。
  • 在构建器中:一个non-static no-final字段对应目标的一个参数
  • 在构建器中:一个私有的不带参数的空构造函数。
  • 在构建器中:对于目标的每个参数,类似于“setter”的方法:它与该参数和相同名称具有相同的类型。它返回构建器本身,以便可以setter调用可以链式处理,如上例所示。
  • 在构建器中:一个 build() 方法,它调用方法,传入每个字段。它返回与目标返回的相同类型。
  • 在构建器中:一个合理的 toString() 实现。
  • 在包含目标的类中:builde() 方法,它创建构建器的新实例。

如果上述列出的要生成的元素(忽略参数个数并仅查看名称)已经存在,则将以静默方式跳过每个元素。这包括构建器本身:如果该类已经存在,则lombok将简单地开始在此已存在的类中注入字段和方法,当然除非要注入的字段/方法已经存在。你不可以在构建器类上放置任何其他方法(或构造函数)生成lombok注释;例如,你不能将 @EqualsAndHashCode 放在构建器类上。 @Builder 可以为集合参数/字段生成所谓的“singular”方法。这些采用1个元素而不是整个列表,并将该元素添加到列表中。例如: Person.builder().job("Mythbusters").job("Unchained Reaction").build(); 将导致 List<String> jobs 字段中包含2个字符串。要获得此行为,需要使用@Singular注释此字段/参数。该功能有自己的文档

既然“method”模式已经清楚了,那么在构造函数上放置一个@Builder 注释的功能也是类似的。实际上,构造函数只是具有特殊语法来调用它们的静态方法:它们的“返回类型”是它们构造的类,它们的类型参数与类本身的类型参数相同。

最后,将 Builder 用于类就好像你将 @AllArgsConstructor(access = AccessLevel.PACKAGE)添加到类中并将 @Builder 注释应用于此all-args构造函数。这仅适用于您自己没有编写任何显式构造函数的情况。如果您确实有一个显式构造函数,请将 @Builder 注解放在构造函数上而不是类上。

如果使用 @Builder 生成构建器来生成自己类的实例(除非将 @Builder 添加到不返回自己类型的方法中,否则总是如此),你可以使用 @Builder(toBuilder = true) 来生成你的类中的一个叫toBuilder()的实例方法; 它会创建一个以该实例的所有值开头的新构建器。你可以将 @Builder.ObtainVia 注解放在参数(如果是构造函数或方法)或字段(如果在 @Builder 的类型)上,以指示获取该字段值/参数值的实例替代手段。例如,你可以指定要调用的方法:@Builder.ObtainVia(method = "calculateFoo")

构建器类的名称是 FoobarBuilder,其中Foobar是目标返回类型的简化,标题形式 - 即构造函数和类型上 @Builder 类型的名称,以及给在方法上的 @Builder 的返回类型的名称。 例如,如果将 @Builder 应用于名为com.yoyodyne.FancyList<T> 的类,则构建器名称将为 FancyListBuilder<T>。如果将 @Builder 应用于返回 void 的方法,则构建器将命名为 VoidBuilder

构建器的可配置方面是:

  • 构建器的类名(默认值:返回类型+'Builder')
  • build() 方法的名称(默认值:“build”)
  • builder() 方法的名称(默认值:“builder”)
  • 如果你想要 toBuilder( ) (默认值:no)
  • 所有生成元素的访问级别(默认值:public)。

所有选项都从默认值更改的示例用法: @Builder(builderClassName = "HelloWorldBuilder", buildMethodName = "execute", builderMethodName = "helloWorld", toBuilder = true, access = AccessLevel.PRIVATE)

Builder.Default

如果在 build 会话期间从未设置某个字段/参数,则它始终为 0 / null / false。 如果你将 @Builder 放在类(而不是方法或构造函数)上,则可以直接在字段上指定默认值,并使用 @Builder.Default 注释该字段: @Builder.Default:@Builder.Default private final long created = System.currentTimeMillis();

@Singular

通过使用@ Singular 注解注释其中一个参数(如果使用 @Builder 注释方法或构造函数)或字段(如果使用 @Builder 注释类),lombok会将该构建器节点视为集合,并生成2个 'adder' 方法而不是一个 'setter' 方法。一个方法将一个元素添加到集合中,另一个方法将另一个集合的所有元素添加到本集合中。不仅生成(替换已添加的任何内容)的setter。还生成了 'clear' 方法。这些“singular”构建器非常复杂,以保证以下属性:

  • 当调用 build( ) 时,生成的集合将是不可变的。
  • 在调用 build( ) 之后调用 'adder' 方法或 'clear' 方法不会修改任何已生成的对象,如果稍后再调用build(),则会创建自创建以来添加了所有元素的一个集合。
  • 生成的集合将被压缩到最小的可行格式,同时保持高效。

@Singular 只能应用于lombok已知的集合类型。目前,支持的类型是:

  • java.util 中:
    • IterableCollectionList(在一般情况下由压缩的不可修改的 ArrayList 支持)。
    • SetSortedSetNavigableSet(在一般情况下由智能大小的不可修改的 HashSetTreeSet 支持)。
    • MapSortedMapNavigableMap(在一般情况下由智能大小的不可修改的 HashMapTreeMap支持)。
  • Guavacom.google.common.collect
    • ImmutableCollectionImmutableList(由 ImmutableList 的 buider 功能支持)。
    • ImmutableSetImmutableSortedSet(由这些类型的 builder 功能支持)。
    • ImmutableMapImmutableBiMapImmutableSortedMap(由这些类型的构建器功能支持)。
    • ImmutableTable(由ImmutableTable的构建器功能支持)。 如果你的标识符是用普通英语编写的,则 lombok 假定其上带有@Singular 的任何集合的名称是英语复数,并将尝试自动单数化该名称。如果可以,add-one 方法将使用这个单数化名称。例如,如果你的集合称为 statuses ,则add-one方法将自动调用 status 。你还可以通过将单数形式作为参数传递给注解来明确指定标识符的单数形式,如下所示:@Singular("axis") List<Line> axes;.

如果 lombok 无法单数化标识你的标识符,或者它不明确,则lombok 将生成错误并强制你明确指定单数名称。

下面的代码段没有显示 lombok@Singular 字段/参数生成的内容,因为它相当复杂。您可以在此处查看代码段。

With Jackson

你可以自定义构建器的部分,例如向构建器类添加另一个方法,或者在构建器类中注释方法。 Lombok 将生成你不手动添加的所有内容,并将其放入此构建器类中。 例如,如果您尝试将jackson配置为对集合使用特定子类型,则可以编写如下内容:

@Value @Builder
@JsonDeserialize(builder = JacksonExample.JacksonExampleBuilder.class)
public class JacksonExample {
        @Singular private List<Foo> foos;
        
        @JsonPOJOBuilder(withPrefix = "")
        public static class JacksonExampleBuilder implements JacksonExampleBuilderMeta {
        }
        
        private interface JacksonExampleBuilderMeta {
                @JsonDeserialize(contentAs = FooImpl.class) JacksonExampleBuilder foos(List<? extends Foo> foos)
        }
}

With Lombok

import lombok.Builder;
import lombok.Singular;
import java.util.Set;

@Builder
public class BuilderExample {
  @Builder.Default private long created = System.currentTimeMillis();
  private String name;
  private int age;
  @Singular private Set<String> occupations;
}

Vanilla Java

import java.util.Set;
public class BuilderExample {
  private long created;
  private String name;
  private int age;
  private Set<String> occupations;
  
  BuilderExample(String name, int age, Set<String> occupations) {
    this.name = name;
    this.age = age;
    this.occupations = occupations;
  }
  
  private static long $default$created() {
    return System.currentTimeMillis();
  }
  
  public static BuilderExampleBuilder builder() {
    return new BuilderExampleBuilder();
  }
  
  public static class BuilderExampleBuilder {
    private long created;
    private boolean created$set;
    private String name;
    private int age;
    private java.util.ArrayList<String> occupations;
    
    BuilderExampleBuilder() {
    }
    
    public BuilderExampleBuilder created(long created) {
      this.created = created;
      this.created$set = true;
      return this;
    }
    
    public BuilderExampleBuilder name(String name) {
      this.name = name;
      return this;
    }
    
    public BuilderExampleBuilder age(int age) {
      this.age = age;
      return this;
    }
    
    public BuilderExampleBuilder occupation(String occupation) {
      if (this.occupations == null) {
        this.occupations = new java.util.ArrayList<String>();
      }
      
      this.occupations.add(occupation);
      return this;
    }
    
    public BuilderExampleBuilder occupations(Collection<? extends String> occupations) {
      if (this.occupations == null) {
        this.occupations = new java.util.ArrayList<String>();
      }

      this.occupations.addAll(occupations);
      return this;
    }
    
    public BuilderExampleBuilder clearOccupations() {
      if (this.occupations != null) {
        this.occupations.clear();
      }
      
      return this;
    }

    public BuilderExample build() {
      // complicated switch statement to produce a compact properly sized immutable set omitted.
      Set<String> occupations = ...;
      return new BuilderExample(created$set ? created : BuilderExample.$default$created(), name, age, occupations);
    }
    
    @java.lang.Override
    public String toString() {
      return "BuilderExample.BuilderExampleBuilder(created = " + this.created + ", name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")";
    }
  }
}

支持的配置键:

lombok.builder.flagUsage = [ warning | error ](默认:未设置)

如果已配置,Lombok 会将 @Builder 的任何用法标记为警告或错误。 lombok.singular.useGuava = [ true | false ](默认值:false)

如果为 true ,lombok 将使用 guava 的 ImmutableXxx 构造器和类型来实现 java.util 集合接口,而不是基于Collections.unmodifiableXxx 创建实现。如果使用此设置,则必须确保 guava 实际上在类路径和构建路径上可用。如果你的字段/参数具有guava ImmutableXxx 类型之一,则会自动使用Guava。

lombok.singular.auto = [ true | false ](默认值:true) 如果为true(这是默认值),lombok会自动尝试通过假设它是一个常见的英语复数来单数化你的标识符名称。如果为false,则必须始终显式指定单数名称,如果不这样做,则lombok将生成错误(如果您使用英语以外的语言编写代码,这会很有用)。

Small print

@Singularjava.util.NavigableMap / Set 的支持仅在使用JDK1.8或更高版本进行编译时才有效。

你无法手动提供 @Singular 节点的部分或全部部分; Lombok生成的代码太复杂了。如果要手动控制(部分)与某个字段或参数关联的构建器代码,请不要使用 @Singular 并手动添加所需的所有内容。

已排序的集合(java.util:SortedSetNavigableSetSortedMapNavigableMap和guava:ImmutableSortedSetImmutableSortedMap)要求集合的类型参数具有自然顺序(实现 java.util.Comparable)。无法通过显式 Comparator 在构建器中使用。

如果目标集合来自 java.util 包,则 ArrayList 在调用@Singular标记字段的方法时用于存储添加的元素,即使集合是set或map也是如此。因为lombok确保生成的集合被压缩,所以无论如何都必须构造集合或映射的新后备实例,并且在build过程中将数据存储为 ArrayList 比将其存储为map或set更有效。此行为不是外部可见的,@Singular @Builder 参考了当前 java.util 的实现细节。

使用 toBuilder = true 应用于方法时,带注释的方法本身的任何类型参数也必须显示在返回类型中。

@Builder.Default 字段上的初始化程序将被删除并存储在静态方法中,以保证如果在构建中指定了值,则根本不会执行此初始化程序。这确实意味着初始化程序不能引用 thissuper或任何非静态成员。如果lombok为你生成构造函数,它还将使用初始化程序初始化此字段。

各种众所周知的关于空值的注释会导致插入空值检查,并将其复制到参数中。有关详细信息,请参阅 Getter/ Setter 文档的small print.

你禁止生成 builder( ) 方法, 例如因为你只需要 toBuilder( )功能, 可以通过使用:@Builder(builderMethodName = "") 。当你执行此操作时,有关缺少@Builder.Default注释的任何警告都将消失,因为仅当使用 toBuilder( ) 创建构建器实例时,此类警告不相关。

你可以将 @Builder 用于复制构造函数:foo.toBuilder().build() 进行浅拷贝。如果你只想要此功能,请考虑使用以下方法禁止生成构建器方法:@Builder(toBuilder = true, builderMethodName = "")

由于javac处理静态导入的特殊方式,尝试对静态 builde() 方法进行非星形静态导入将不起作用。要么使用星型静态导入:import static TypeThatHasABuilder.*; 或者不要静态导入builder方法。

如果将访问级别设置为 PROTECTED ,则构建器类中生成的所有方法实际上都是 public 生成的; protected 关键字的含义在内部类中是不同的,并且 PROTECTED 指示的精确行为(允许同一个包中的任何源访问,以及这些类的任何外部子类,用@Builder 标记是不可能的)并且标记内部成员 public 是尽可能接近的。