今天让我们探讨一下如何利用 Lombok 工具来提升 Java 开发中的效率。提到 Java,很多人的第一反应就是臃肿,这主要是因为在日常编程中,我们不得不编写许多重复的代码,例如对类的属性写的 Setter、Getter 方法,下面就是一个简单的例子:
public class Student { private int age; private String name; }
在上面的代码中,我们定义了一个名为 Student 的类,并为其添加了两个属性:age 和 name。如果我们要在其他地方使用这个类,还需要为这个类声明访问方法,也就是 Getter 和 Setter 方法。现在一些主流的 IDE,比如 IntelliJ IDEA,提供了可以自动生成 Getter 和 Setter 函数的方法。
下图是在 IDEA 中,单击右键时生成 Getter/Setter 方法的截图(我们可以看到 IDEA 还支持生成其他常用的方法,比如 equals()、hashCode()、toString() 等等。
在使用 IDEA 自动填充了 Getter 和 Setter 方法之后,Student 类的代码感觉瞬间臃肿了很多,具体代码如下:
public class Student { private int age; private String name; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
在我们创建一个类之后,除了一般会用到的 Getter 和 Setter 方法之外,我们有时候可能还需要用到构造方法和 toString 方法,把这两个方法补充完整之后 student 类的代码如下所示:
public class Student { private int age; private String name; public Student() { } public Student(int age, String name) { this.age = age; this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
看完之后,不得不说这样自动补全出来的代码实在是太臃肿了,其实我们关心就是前面两行代码,知道有哪些成员,剩下的 Getter/Setter/toString 不看也可以。不管对于写代码的同学,还是看代码的同学,这些冗余代码都是一种负担。
那么我们不禁要问,对于这些固定模式的代码有没有一种精简的处理方式呢?答案是肯定的,对于这个问题,有一种比较主流的解决方案——就是 Lombok。
Lombok 是什么
在 Lombok 的官网上,对于 Lombok 的介绍如下:
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
简单来说,就像上面这段介绍提到的,Lombok 是一个 Java 库,也可以看作是一个开发工具插件。我们可以通过注解的方式引入 Lombok,在编译期间,它会自动为我们生成一些固定模式的代码,比如 Getter/Setter 等。
引入 Lombok ,可以让我们的代码变得更加简洁,也能够让我们更专注于主要的逻辑上,同时也能减少我们编写代码时可能会出现的 bug。
如何使用 Lombok
还是以上面的代码为例,看看如何使用 Lombok 来简化这些冗余的代码呢?首先,我们要在项目中引入 Lombok 的依赖。不过要注意的是,要是使用 IDE 进行开发,那么还需要安装一下对应的 Lombok 插件。
下面是对引入 Lombok 依赖的代码演示,这里的代码是基于 JDK 8 进行的,当然 JDK 11 也是类似的。
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency>
Getter/Setter
既然每一个类中最常见的冗余代码就是 getter 方法和 setter 方法,那么我们就来先看一下如何使用 Lombok 来处理这两个常见的冗余方法。简单来说,我们通过注解 @Getter/@Setter 修饰 Class 的成员的这种做法,等价于为其生成 Getter 和 Setter 方法这种做法。
下面的代码所展示的,是使用注解 @Getter/@Setter 来避免 Getter 和 Setter 方法冗余的具体操作。
import lombok.Getter; import lombok.Setter; public class Student { @Getter @Setter private int age; @Getter @Setter private String name; }
当然,这时候我们还可以通过其他注解来生成其他需要的方法:
@ToString 为其生成 toString() 方法;
@EqualsAndHashCode 生成 equals() 和 hashCode() 方法。
但是,如果需要这么多功能方法的话,我们一般选择注解 @Data。
@Data
之所以注解 @Data 能够应用于需要多种功能的方法,则因为注解 @Data 实际上是多个注解组成的一个注解集合。注解 @Data 等价于如下注解的集合:
@Getter 会被添加到所有成员变量;
@Setter 添加到所有非 final 的成员变量(final 成员不能用户 Setter 方法);
@ToString;
@EqualsAndHashCode;
@RequiredArgsConstructor 生成以特定参数(包括 final 和特定参数如 @NonNull 修饰的成员变量)为入参的构造函数。
下面我们用注解 @RequiredArgsConstructor 来举个例子:
@RequiredArgsConstructor @ToString public class Student { private int age; private final int number; @NotNull private String name; public static void main(String[] args) { Student student = new Student(1, "xiaoming"); System.out.println(student); } }
在上面的代码片段中,注解 @RequiredArgsConstructor 等价于如下的构造函数:
public class Student { public Student(int number, String name) { this.number = number; this.name = name; } }
所以,上面使用的注解 @RequiredArgsConstructor 的代码的输出应该为:
Student(age=0, number=1, name=xiaoming)
NoArgsConstructor/AllArgsConstructor
针对构造函数,除了注解 @RequiredArgsConstructor,Lombok 还提供了另外两个选择:
@NoArgsConstructor 不接受参数的构造函数,这种情况下要求 Class 内没有 final 修饰的成员变量
@AllArgsConstructor 每个成员变量都要接受一个参数的构造函数
为方便起见,我们在一个 Student 类中同时添加 @NoArgsConstructor 和 @AllArgsConstructor 两个注解(构造函数重载)做展示,下面是具体的代码示例:
@NoArgsConstructor @AllArgsConstructor public class Student { private int age; private String name; }
上面的代码等价于:
public class Student { private int age; private String name; public Student() {} public Student(int age, String name) { this.age = age; this.name = name; } }
log
除了上面我们讲到的对诸多方法的支持,Lombok 还提供对 log 的支持,也就是为日志的输出和打印提供了便利。在不使用 Lombok 的情况下,如果要打印 log,那么就需要自己初始化一个 org.slf4j.Logger
的实例。
初始化 org.slf4j.Logger
的具体代码如下:
public class Student { private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(Student.class); public void foo() { LOGGER.info(...); } }
同上面初始化 org.slf4j.Logger
的代码相比,使用 Lombok 输出和打印日志就会简单很多,具体代码如下:
@Slf4j public class Student { public void foo() { log.info(...); } }
Builder
借助于注解 @Builder 我们可以以 Fluent 的风格进行编程。Fluent 风格是一种链式的编程方式,比如在 curator API 中(Curator 是 Netflix 公司开源的一套 Zookeeper 客户端框架,解决了很多 Zookeeper 客户端非常底层的细节开发工作,包括连接重连、反复注册 Watcher 和 NodeExistsException 异常等等),就大量使用 Fluent 的编程风格(如下)。这种方式在构造对象参数不定的情况下会比较好用。
下面的代码构造了一个 CuratorFramework 的 Client,这里就采用了 Fluent 编程风格。
CuratorFramework client = CuratorFrameworkFactory.builder() .connectString("domain.book.zookeeper:2181") .sessionTimeoutMs(5000) .retryPolicy(retryPolicy) .build()
通过注解 @Builder 使用 Fluent 风格也比较简单,直接在 Class 上面添加注解即可,具体的代码示例如下:
@Builder public class Student { private int age; private int number; private String name; public static void main(String[] args) { Student student = Student.builder() .age(18) .name("Jack") .number(1000) .build(); } }
从上面的代码中,我们能看到这种链式的编程方式优势在于构造对象的时候可以初始化任意成员,而不需要定义构造函数,这为我们代码带来了极大的简洁性。但注解 @Builder 有一个缺点:在子类中无法通过 builder
方法构造父类中的成员变量。对于这种情况我们可以使用 @SuperBuilder 来替换。
至于 Lombok 剩下的注解这里就不再详述了,你可以参考: projectlombok.org/features/al…
Lombok 的缺点
最后我们再说说 Lombok 的缺点,毕竟凡事都是有两面性的,主要有以下两点:
需要 IDE 支持 “annotation processing”,也就是 IDE 要支持对注解的处理,目前的主流 Java IDE 都已经支持了,比如 IntelliJ IDEA、NetBeans、Eclipse。
和编译器强绑定。Lombok 会在编译期间调用编译器的 API 生成中间代码。这个过程可能会调用 non-public API 进而导致兼容性问题:也就是说,一个使用 Lombok 的项目,在 JDK 7 下是正常运行的,但是升级到 JDK 8 之后不行了。这个时候我们往往要顺带升级一下 Lombok 的版本。
总结
今天分享的内容比较简单,就是在日常开发中如何通过引入 Lombok 来精简我们的代码,进而极大提高开发效率。在下面,我对 Lombok 中的不同注解用脑图的方式做了总结。