Java 是一种伟大但冗长的语言。即使是为了实现最常见的目标,您最终也可能会编写很多行代码。另外,它肯定涉及重复的代码,例如 getter 和 setter。这导致了大量的样板和可避免的代码。这不仅不会增加应用程序的业务逻辑,而且编写它是一个不必要的无聊和耗时的过程。这就是为什么您应该开始使用工具和库来避免这种情况,从而提高您的工作效率。这就是龙目岛发挥作用的地方!
这个 Java 库为您提供了几个注解,旨在避免编写已知重复和/或样板的 Java 代码。Project Lombok 通过插入您的构建过程来工作。然后,它会根据您使用的注释将 Java 字节码自动生成到实现所需行为所需的.class文件中。因此,Project Lombok 提供的每个注释都允许您跳过编写您想避免的方法和逻辑,例如构造函数、equals 和哈希代码函数。这将为您节省大量时间,并让您专注于项目的业务逻辑。此外,您将能够使您的代码库更小、更干净、更易于阅读和维护。
首先,我们将了解 Project Lombok 是什么以及它是如何工作的。然后,我们将研究最常见和相关的 Lombok 注释,了解最重要的注释是什么,在哪里以及如何使用它们。接下来,是时候看看如何将它集成到你的 IDE(集成开发环境)中了,以及为什么你不应该害怕使用它。
先决条件
这是复制接下来将显示的示例的所有先决条件的列表:
- Java >= 1.8
- Gradle >= 4.x 或 Maven 3.6.x
- 龙目岛项目 >= 1.18.20
什么是龙目岛
Project Lombok(从现在开始,Lombok)是一个基于注解的 Java 库,可让您减少样板代码。Lombok 提供了各种注解,旨在替换以样板、重复或繁琐而著称的 Java 代码。例如,通过使用 Lombok,您可以避免编写不带参数的构造函数、、和方法,只需添加一些注释即可。当库将表示所需代码和样板代码的字节码注入到您的.class文件中时,会在编译时发生奇迹。另外,正如我们将看到的,该库可以插入到您的 IDE 中,让您拥有与自己编写所有样板代码相同的体验。toString()
equals()
hashCode()
lombok您可以通过添加依赖项来轻松安装 Lombok 。如果您是Gradle 用户,请将这两行添加到文件的依赖项部分:build.gradle
compileOnly 'org.projectlombok:lombok:1.18.20'
annotationProcessor 'org.projectlombok:lombok:1.18.20'
如果您是Maven 用户,请将以下依赖项添加到您的文件中:pom.xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
另外,将 Lombok 依赖项添加到配置部分,如下所示:maven-compiler-plugin
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>11</source> <!-- depending on your project -->
<target>11</target> <!-- depending on your project -->
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- ... -->
</plugins>
</build>
现在,您拥有开始使用 Lombok 所需的一切。
最常见的龙目岛注解
在这里您可以找到最常见和最重要的 Lombok 注释。将解释它们中的每一个,然后将其与等效的 Java vanilla 翻译进行比较。要查看示例并获得更多支持,请单击每个注释并访问Lombok 官方文档上的页面。
@Getter,@Setter
当使用@Getter
和/或注释字段时@Setter
,Lombok 将分别自动生成默认的 getter 和/或 setter。getter 的默认实现只负责返回带注释的字段。类似地,setter 的默认实现采用一个与注释字段相同类型的参数,并简单地用接收到的值设置它。当一个被调用的字段同时用andvalue
注释时,Lombok 将定义一个(或者如果该字段是)和一个方法。除非特别指定,否则生成的 getter/setter 方法将是 。允许的值为、、和@Getter
@Setter
getValue()
isValue()
boolean
setValue()
public
AccessLevel
AccessLevel
PUBLIC
PROTECTED
PACKAGE
PRIVATE
. 请注意,您也可以对整个班级进行注释。在这种情况下,此逻辑将应用于每个字段。
与龙目岛
@Getter
@Setter
public class Author {
private int id;
private String name;
@Setter(AccessLevel.PROTECTED)
private String surname;
}
爪哇香草
public class User {
private int id;
private String name;
private String surname;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
protected void setSurname(String surname) {
this.surname = surname;
}
}
@NoArgsConstructor, @RequiredArgsConstructor,@AllArgsConstructor
当一个类被注解时@NoArgsConstructor
,Lombok 会自动生成一个不带参数的构造函数。同样,当使用 注释时@AllArgsConstructor
,将生成一个为您的类中的每个字段提供一个参数的构造函数。类似地,@RequiredArgsConstructor
对于需要特殊处理的每个字段,都会导致一个带有参数的构造函数。特别是,这涉及未初始化的final
字段,以及任何标记为@NonNull
在声明时未初始化的字段。请不要忘记这些注释将忽略静态字段。
与龙目岛
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
public class Author {
private int id;
private String name;
private String surname;
private final String birthPlace;
}
爪哇香草
public class Author {
private int id;
private String name;
private String surname;
private final String birthPlace;
// @NoArgsConstructor
public Author() {}
// @AllArgsConstructor
public Author(int id, String name, String surname, String birthPlace) {
this.id = id
this.name = name
this.surname = surname
this.birthPlace = birthPlace
}
// @RequiredArgsConstructor
public Author(String birthPlace) {
this.birthPlace = birthPlace
}
}
@ToString
当一个类用 注释时@ToString
,Lombok 将负责生成该方法的正确实现。默认情况下,将返回一个包含类名的字符串,后跟以逗号分隔的每个字段的值。通过将参数设置为true,每个字段的名称将放在其值之前。默认情况下,生成方法时将考虑所有非静态字段。用 注释字段以使 Lombok 忽略它。或者,您可以使用 指定您希望考虑的字段。然后,用 标记要包含的每个字段。toString()
includeFieldNames
toString()
@ToString.Exclude
@ToString(onlyExplicitlyIncluded = true)
@ToString.Include
与龙目岛
@ToString(includeFieldNames=true)
public class Author {
private int id;
private String name;
private String surname;
}
爪哇香草
public class Author {
private int id;
private String name;
private String surname;
@Override
public String toString() {
return "Author(id=" + this.id + ", name=" + this.name + ", surnname=" + this.surname + ")";
}
}
@EqualsAndHashCode
用 注释一个类@EqualsAndHashCode
,Lombok 会自动为你实现和方法。默认情况下,将考虑所有非静态、非瞬态字段。您可以通过使用或注释来修改使用哪些字段。或者,您可以使用 注释您的类,然后通过用 标记它们来准确指定要使用的字段或方法。请注意) 和) 方法将由 Lombok 生成,而不会破坏它们之间的约定。按照官方 Java 文档的这两种方法的链接,了解更多关于应履行的合同和实现的信息。equals()
hashCode()
@EqualsAndHashCode.Include
@EqualsAndHashCode.Exclude
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@EqualsAndHashCode.Include
equals()hashCode()equals()
hashCode()
与龙目岛
@Getter
@Setter
@EqualsAndHashCode
public class Author {
private int id;
private String name;
private String surname;
}
爪哇香草
public class Author {
// gettes and setters ...
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((surname == null) ? 0 : surname.hashCode());
return result;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Author)) return false;
Author other = (Author) o;
if (!other.canEqual((Object)this)) return false;
if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false;
if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
if (this.getSurname() == null ? other.getSurname() != null : !this.getSurname().equals(other.getSurname())) return false;
return true;
}
}
@NonNull
@NonNull
您可以使用记录组件、方法的参数或整个构造函数进行注释。这样,Lombok 将相应地为您生成空检查语句。
与龙目岛
public class Author {
private int id;
private String name;
private String surname;
public Author(
@NonNull int id,
@NonNull String name,
String surname
) {
this.id = id;
this.name = name;
this.surname = surname;
}
}
爪哇香草
public class Author {
private int id;
private String name;
private String surname;
public Author(
int id,
String name,
String surname
) {
if (id == null) {
throw new NullPointerException("id is marked @NonNull but is null");
}
this.id = id;
if (name == null) {
throw new NullPointerException("name is marked @NonNull but is null");
}
this.name = name;
this.surname = surname;
}
}
@Data
@Data
是结合了 、 、 和 的特征@ToString的@EqualsAndHashCode快捷注释。因此,生成 POJO( Plain Old Java Objects)中涉及的所有样板。这尤其意味着所有字段的 getter、所有非 final 字段的 setter、正确的、和涉及类的每个字段的实现,以及所有 final 字段的构造函数。@Getter @Setter@RequiredArgsConstructor@Data
toString
equals
hashCode
与龙目岛
@Data
public class Author {
private final int id;
private String name;
private String surname;
}
爪哇香草
public class Author {
private final int id;
private String name;
private String surname;
public Author(int id) {
this.id = id;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = prime * result + getId();
result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
result = prime * result + ((getSurname() == null) ? 0 : getSurname().hashCode());
return result;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Author)) return false;
Author other = (Author) o;
if (!other.canEqual((Object)this)) return false;
if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false;
if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
if (this.getSurname() == null ? other.getSurname() != null : !this.getSurname().equals(other.getSurname())) return false;
return true;
}
}
@Value
@Value
是 的不可变变体@Data。这意味着所有字段都是由 Lombok 默认创建private
的。final
另外,不会生成 setter,类本身将被标记为final
. 这样,该类将不可继承。就像 , , 发生的事情一样,@Data
也创建了实现。toString()
equals()
hashCode()
与龙目岛
@Data
public class Author {
int id;
String name;
String surname;
}
爪哇香草
public final class Author {
private final int id;
private final String name;
private final String surname;
public Author(int id, String name, String surname) {
this.id = id
this.name = name
this.surname = surname
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = prime * result + getId();
result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
result = prime * result + ((getSurname() == null) ? 0 : getSurname().hashCode());
return result;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Author)) return false;
Author other = (Author) o;
if (!other.canEqual((Object)this)) return false;
if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false;
if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
if (this.getSurname() == null ? other.getSurname() != null : !this.getSurname().equals(other.getSurname())) return false;
return true;
}
}
高级龙目岛注解
在这里您可以找到最复杂的 Lombok 注释。将解释它们中的每一个,然后将其与等效的 Java vanilla 翻译进行比较。要查看示例并获得更多支持,请单击每个注释并访问Lombok 官方文档上的页面。
@Cleanup
@Cleanup
可用于确保在离开当前范围之前自动清理给定资源。默认情况下,注释资源的清理方法假定为,但您可以指定要调用的所需方法的名称。请注意,此注释通过利用try-with-resources 语句起作用。close()
与龙目岛
public class CleanupDemo {
public static void main(String[] args) throws IOException {
@Cleanup
InputStream input = new FileInputStream(args[0]);
@Cleanup
OutputStream output = new FileOutputStream(args[1]);
byte[] b = new byte[10000];
while (true) {
int r = input.read(b);
if (r == -1)
break;
output.write(b, 0, r);
}
}
}
爪哇香草
public class CleanupDemo {
public static void main(String[] args) throws IOException {
try (OutputStream output = new FileOutputStream(args[1])) {
try (InputStream input = new FileInputStream(args[0])) {
byte[] b = new byte[10000];
while (true) {
int r = input.read(b);
if (r == -1)
break;
output.write(b, 0, r);
}
}
}
}
}
@Synchronized
@Synchronized
允许您实现类似于synchronized关键字的功能,但锁定不同的对象。关键字锁定在this
,而注释锁定在一个名为 的特殊私有字段上$lock
。如果此字段不存在,它将由 Lombok 创建。这是默认行为,但您也可以自己指定锁定对象。在处理static
方法时,注解将锁定一个名为 的静态字段。请考虑,就像 一样,注解只能用于静态和实例方法。$LOCK
synchronized
与龙目岛
public class SynchronizedDemo {
private final Object objectToLock = new Object();
@Synchronized
public static void sayHello() {
System.out.println("Hello!");
}
@Synchronized
public int getOne() {
return 1;
}
@Synchronized("objectToLock")
public void printObject() {
System.out.println(objectToLock);
}
}
爪哇香草
public class SynchronizedDemo {
private static final Object $LOCK = new Object[0];
private final Object $lock = new Object[0];
private final Object readLock = new Object();
public static void sayHello() {
synchronized($LOCK) {
System.out.println("Hello");
}
}
public int getOne() {
synchronized($lock) {
return 1;
}
}
public void printObject() {
synchronized(readLock) {
System.out.println(objectToLock);
}
}
}
@SneakyThrows
@SneakyThrows
可以用来偷偷抛出已检查的异常,而无需throws
像通常那样在方法的子句中实际声明它们。因此,此注释允许您通过安静地处理所有检查的异常来完全避免所需的块。Lombok 不会忽略、包装、替换或修改抛出的已检查异常。反而会误导编译器。事实上,在 JVM(Java 虚拟机)类文件级别,无论您的方法的子句如何,都可以抛出所有异常,这就是它起作用的原因。此注释可能很危险,应谨慎使用。这就是为什么您应该从 Lombok 官方文档中阅读此页面以了解有关何时以及如何使用它的更多信息。try-catch
throws
@Builder
您可能需要开发一个构建器对象,允许您按照分步构建模式创建对象,例如. 这在处理具有多个字段的大型类时特别有用。您可以使用这种更具可读性的方法,而不是使用带有许多参数的构造函数。通过使用注释,您可以让 Lombok 为您生成构建器。通过用 注释一个类,Lombok 将生成一个实现上述构建器模式的类。例如,通过注释类,将自动生成一个类。由于您的构建器的行为可能很复杂或高度定制,Lombok 提供了许多参数来实现所需的结果。你可以在这里找到它们。Author.builder().id("1").name("Maria").surname("Williams").build();
@Builder
@Builder
Author
AuthorBuilder
@Log
大多数记录器要求您在要记录的每个类中设置一个记录器实例。这肯定涉及样板代码。通过使用 注释类@Log
,Lombok 将自动添加一个静态的 finallog
字段,根据您的日志库的要求进行初始化。这就是 Lombok 为每个最流行的日志框架为开发人员提供注释的原因。您可以在此处找到完整列表。
龙目岛插件
最流行和最广泛使用的 IDE 附带一个官方 Lombok 插件,旨在帮助开发人员使用 Lombok。特别是,它通过提供最常见的 Lombok 注释的快捷方式来支持您。此外,它还会根据您单击的位置向您建议您可能需要或正在寻找的注释。在编写IntelliJ IDEA时,正式支持Eclipse、Spring Tool Suite、(Red Hat) JBoss Developer Studio、MyEclipse、Microsoft Visual Studio Code和Netbeans。按照与您的 IDE 相关的链接获取有关如何安装它的支持。访问Lombok网站以获取所有支持的 IDE 的完整列表。
使用龙目岛有风险吗?
您可能担心在整个代码库中传播 Lombok 注释。事实上,如果你决定避免使用它会发生什么?你可能会发现自己被困住了。好吧,这不是一个真正的问题,因为 Lombok 带有一个delombok工具。如官方文档中所述,虽然没有涵盖所有可能的 IDE 和案例,但它使从 Lombok 中释放代码的过程更容易。该工具的作用是自动生成 Java 源代码,其中包含 Lombok 将注入的字节码中包含的相同功能。这样,您的 Lombok 注释代码库将简单地替换为标准的非 Lombok Java 代码库。因此,您的整个项目将不再依赖于 Lombok。因此,要回答最初的问题,不,根本不是。使用 Lombok 并不代表对未来或您的项目有风险。
结论
在本文中,我们研究了如何使用 Project Lombok,一个自动插入编辑器并构建工具的 Java 库,以避免编写 Java 众所周知的样板、无聊和重复代码。如图所示,这是一种使您成为更高效的开发人员并且不会将时间浪费在繁琐的活动上的简单方法。通过开始利用其最相关的注释,您可以避免编写从项目的业务逻辑角度来看没有实际价值的数千行代码。另外,总有可能使您的项目不再容易依赖 Project Lombok。因此,使用它并不代表发现自己被锁定的风险。总之,每个 Java 开发人员都应该使用 Project Lombok,并解释开始使用它所需的一切是本文的目的。