1. 概述
Project Lombok 通过提供自动生成常用代码的注解,减少了 Java 应用程序中的样板代码。
在这个教程中,我们将探讨此库提供的三种构造函数注解之间的差异。
2. 设置
为了突出这些差异,我们首先在依赖项中添加lombok:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
接下来,我们创建一个类作为演示的基础:
public class Person {
private int age;
private final String race;
@NonNull
private String name;
private final String nickname = "unknown";
}
我们在 Person 对象中故意散布了各种不同的访问修饰符,每个构造函数注解处理方式不同。在接下来的每个部分,我们将使用这个类的不同名称的副本。
3. @AllArgsConstructor
顾名思义,**@AllArgsConstructor 注解会生成一个初始化所有对象字段的构造函数**。带有 @NonNull 标记的字段会在生成的构造函数中进行 null 检查。
让我们将注解添加到类中:
@AllArgsConstructor
public class AllArgsPerson {
// ...
}
然后,我们在生成的构造函数中触发 null 检查:
@Test
void whenUsingAllArgsConstructor_thenCheckNotNullFields() {
assertThatThrownBy(() -> {
new AllArgsPerson(10, "Asian", null);
}).isInstanceOf(NullPointerException.class)
.hasMessageContaining("name is marked non-null but is null");
}
@AllArgsConstructor 提供了一个包含对象所有必要字段的 AllArgsPerson 构造函数。
4. @RequiredArgsConstructor
@RequiredArgsConstructor 注解会生成一个只初始化标记为 final 或 @NonNull 的字段的构造函数,前提是它们在声明时未被初始化。
让我们更新我们的类以使用 @RequiredArgsConstructor:
@RequiredArgsConstructor
public class RequiredArgsPerson {
// ...
}
对于 RequiredArgsPerson 对象,这将导致只有两个参数的构造函数:
@Test
void whenUsingRequiredArgsConstructor_thenInitializedFinalFieldsWillBeIgnored() {
RequiredArgsPerson person = new RequiredArgsPerson("Hispanic", "Isabela");
assertEquals("unknown", person.getNickname());
}
由于我们初始化了 nickname 字段,尽管它是 final,但它不会成为生成构造函数参数的一部分。相反,它被视为其他非 final 字段和未标记为 NotNull 的字段。
与 @AllArgsConstructor 类似,@RequiredArgsConstructor 注解也会对带有 @NonNull 标记的字段执行 null 检查,如我们在单元测试中所示:
@Test
void whenUsingRequiredArgsConstructor_thenCheckNotNullFields() {
assertThatThrownBy(() -> {
new RequiredArgsPerson("Hispanic", null);
}).isInstanceOf(NullPointerException.class)
.hasMessageContaining("name is marked non-null but is null");
}
在使用 @AllArgsConstructor 或 @RequiredArgsConstructor 时,维护对象字段的顺序至关重要。例如,如果我们交换了 Person 对象中的 name 和 race 字段,由于它们具有相同的类型,编译器不会抱怨。然而,我们的库的现有用户可能会忽视调整构造参数的需要。
5. @NoArgsConstructor
通常,如果我们没有定义构造函数,Java 会提供一个默认的构造函数。同样,***@NoArgsConstructor 为一个类生成无参数构造函数,类似于默认构造函数**。我们指定 force 参数标志以避免因未初始化的 final 字段引起的编译错误:
@NoArgsConstructor(force = true)
public class NoArgsPerson {
// ...
}
接下来,让我们检查未初始化字段的默认值:
@Test
void whenUsingNoArgsConstructor_thenAddDefaultValuesToUnInitializedFinalFields() {
NoArgsPerson person = new NoArgsPerson();
assertNull(person.getRace());
assertEquals("unknown", person.getNickname());
}
与其它字段不同,nickname 字段没有收到 null 的默认值,因为我们是在声明时初始化它的。
6. 多个注解的使用
在某些情况下,不同的需求可能需要使用多个注解。例如,如果我们更喜欢提供一个静态工厂方法,但仍然需要一个默认构造函数以与外部框架(如[JPA](/jpa-no-argument-constructor-entity-class))兼容,我们可以使用两个注解:
@RequiredArgsConstructor(staticName = "construct")
@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
public class SpecialPerson {
// ...
}
然后,我们可以用示例值调用我们的静态构造函数:
@Test
void whenUsingRequiredArgsConstructorWithStaticName_thenHideTheConstructor() {
SpecialPerson person = SpecialPerson.construct("value1", "value2");
assertNotNull(person);
}
在这种情况下,尝试使用默认构造函数实例化将导致编译错误。
7. 比较总结
让我们用表格来概括我们讨论的内容:
注解
生成的构造函数参数
@NonNull 字段的 null 检查
@AllArgsConstructor
所有对象字段(除了静态和已初始化的 final 字段)
是
@RequiredArgsConstructor
仅 final 或 @NonNull 字段
是
@NoArgsConstructor
无
否
8. 结论
在这篇文章中,我们探讨了Project Lombok提供的构造函数注解。我们了解到,@AllArgsConstructor 初始化所有对象字段,而@RequiredArgsConstructor 只初始化 final 和 @NotNull 字段。此外,我们发现@NoArgsConstructor 生成类似默认构造函数的构造器,并讨论了如何结合使用这些注解。
一如既往,所有示例的源代码可以在GitHub上找到。