1. Introduction
1.介绍
In this quick tutorial, we’ll investigate how can we provide default values for attributes when using the builder pattern with Lombok.
在这个快速教程中,我们将研究如何在使用Lombok的构建器模式时为属性提供默认值。
Make sure to check out our intro to Lombok as well.
请务必查看我们的龙目岛简介以及。
2. Dependencies
2.依赖性
We’ll use Lombok in this tutorial, so we only need one dependency:
在本教程中,我们将使用Lombok,所以我们只需要一个依赖关系。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
3. POJO With Lombok Builder
3.使用Lombok Builder的POJO
First, let’s have a look at how Lombok can help us get rid of the boilerplate code needed to implement the builder pattern.
首先,让我们看看Lombok如何帮助我们摆脱实现构建器模式所需的模板代码。
We’ll start with a simple POJO:
我们将从一个简单的POJO开始。
public class Pojo {
private String name;
private boolean original;
}
For this class to be useful, we’ll need getters. Also, if we wish to use this class with an ORM, we’ll probably need a default constructor.
为了让这个类变得有用,我们将需要获取器。另外,如果我们希望在ORM中使用这个类,我们可能需要一个默认的构造函数。
On top of these, we want a builder for this class. With Lombok, we can have all this with some simple annotations:
在这些之上,我们想为这个类提供一个构建器。有了Lombok,我们可以通过一些简单的注解来实现这一切。
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Pojo {
private String name;
private boolean original;
}
4. Defining Expectations
4.确定期望值
Let’s define some expectations for what we want to achieve in the form of unit tests.
让我们为我们想以单元测试的形式实现的东西定义一些期望。
The first and most basic requirement is the presence of default values after we build an object with a builder:
第一个也是最基本的要求是在我们用构建器构建一个对象后,默认值的存在。
@Test
public void givenBuilderWithDefaultValue_ThanDefaultValueIsPresent() {
Pojo build = Pojo.builder()
.build();
Assert.assertEquals("foo", build.getName());
Assert.assertTrue(build.isOriginal());
}
Of course, this test fails, since the @Builder annotation doesn’t populate values. We’ll fix this soon.
当然,这个测试是失败的,因为@Builder注解不会填充值。我们将很快解决这个问题。
If we use an ORM, it usually relies on a default constructor. So we should expect the same behavior from the default constructor as we do from the builder:
如果我们使用一个ORM,它通常依赖于一个默认的构造函数。因此,我们应该期望从默认构造函数中获得与构建器相同的行为。
@Test
public void givenBuilderWithDefaultValue_NoArgsWorksAlso() {
Pojo build = Pojo.builder()
.build();
Pojo pojo = new Pojo();
Assert.assertEquals(build.getName(), pojo.getName());
Assert.assertTrue(build.isOriginal() == pojo.isOriginal());
}
At this stage, this test passes.
在这个阶段,这个测试通过了。
Now let’s see how we can make both tests pass.
现在让我们看看如何使这两个测试都通过。
5. Lombok’s Builder.Default Annotation
5.Lombok的Builder.Default注释
Since Lombok v1.16.16, we can use @Builder‘s inner annotation:
从Lombok v1.16.16开始,我们可以使用@Builder的内部注释。
// class annotations as before
public class Pojo {
@Builder.Default
private String name = "foo";
@Builder.Default
private boolean original = true;
}
It’s simple and readable, but it has some flaws.
它很简单,可读性强,但也有一些缺陷。
The default values will be present with the builder, making the first test case pass. Unfortunately, the no-args constructor won’t get the default values, making the second test case fail, even if the no-args constructor isn’t generated, but explicitly written.
默认值将出现在构建器中,使第一个测试案例通过。不幸的是,no-args构造函数不会得到默认值,使得第二个测试案例失败,即使no-args构造函数没有被生成,而是被明确写入。
This side effect of the Builder.Default annotation has been present from the beginning, and it’ll probably be with us for a long time.
Builder.Default注解的这种副作用从一开始就存在,而且它可能会伴随我们很长时间。
6. Initialize the Builder
6.初始化生成器
We can try to make both tests pass by defining default values in a minimalistic builder implementation:
我们可以尝试通过在最小化的构建器实现中定义默认值来使这两个测试通过。
// class annotations as before
public class Pojo {
private String name = "foo";
private boolean original = true;
public static class PojoBuilder {
private String name = "foo";
private boolean original = true;
}
}
This way, both tests will pass.
这样,两个测试都会通过。。
Unfortunately, the price is code duplication. For a POJO with tens of fields, it could be error prone to maintain the double initialization.
不幸的是,其代价是代码的重复。对于一个有几十个字段的POJO来说,维持双重初始化可能会很容易出错。
But if we’re willing to pay this price, we should take care of one more thing, too. If we rename our class using a refactoring within our IDE, the static inner class won’t be automatically renamed. Then Lombok won’t find it and our code will break.
但如果我们愿意付出这样的代价,我们也应该照顾到另外一件事。如果我们在IDE中使用重构来重命名我们的类,静态的内部类将不会被自动重命名。那么Lombok就找不到它,我们的代码就会中断。
To eliminate this risk, we can decorate the builder annotation:
为了消除这种风险,我们可以对构建者注解进行装饰。
// class annotations as before
@Builder(builderClassName = "PojoBuilder")
public class Pojo {
private String name = "foo";
private boolean original = true;
public static class PojoBuilder {
private String name = "foo";
private boolean original = true;
}
}
7. Using toBuilder
7.使用toBuilder
@Builder also supports generating an instance of the builder from an instance of the original class. This feature isn’t enabled by default. We can enable it by setting the toBuilder parameter in the builder annotation:
@Builder还支持从原始类的实例中生成一个构建器的实例。这个功能在默认情况下并没有启用。我们可以通过在构建器注解中设置toBuilder参数来启用它。
// class annotations as before
@Builder(toBuilder = true)
public class Pojo {
private String name = "foo";
private boolean original = true;
}
With this, we can get rid of the double initialization.
有了这个,我们可以摆脱双重初始化。
Of course, there’s a price for that. We have to instantiate the class to create a builder. So we have to modify our tests also:
当然,这是有代价的。我们必须实例化这个类来创建一个构建器。因此我们也必须修改我们的测试。
@Test
public void givenBuilderWithDefaultValue_ThenDefaultValueIsPresent() {
Pojo build = new Pojo().toBuilder()
.build();
Assert.assertEquals("foo", build.getName());
Assert.assertTrue(build.isOriginal());
}
@Test
public void givenBuilderWithDefaultValue_thenNoArgsWorksAlso() {
Pojo build = new Pojo().toBuilder()
.build();
Pojo pojo = new Pojo();
Assert.assertEquals(build.getName(), pojo.getName());
Assert.assertTrue(build.isOriginal() == pojo.isOriginal());
}
Again, both tests pass, so we have the same default value using the no-args constructor as when using the builder.
同样,两个测试都通过了,所以我们使用无args构造函数的默认值与使用构建器时相同。
8. Conclusion
8.结论
In this article, we explored several options to provide default values for the Lombok builder.
在这篇文章中,我们探讨了几种为Lombok构建器提供默认值的方案。
The side effect of the Builder.Default annotation is worth keeping an eye on. But the other options also have their drawbacks, so we have to choose carefully based on the current situation.
Builder.Default注释的副作用值得关注。但其他选项也有其缺点,所以我们必须根据当前的情况谨慎选择。
As always, the code is available over on GitHub.
像往常一样,代码可在GitHub上获得。