一、Lombok简介
Lombok项目是一个Java库,它会自动插入您的编辑器和构建工具中,从而为您的Java增光添彩。
永远不要再编写另一个getter或equals方法,一个带有注释的类将具有功能全面的生成器,自动执行日志记录变量等等。
简而言之,就是自动帮您生成setter和getter,toString、equals等方法。
二、Lombok插件安装
- 1.1 下载Intellij Idea Lombok插件
https://plugins.jetbrains.com/plugin/6317-lombok/versions
选择和版本匹配的插件,否则将可能出错;下载完成后,不需要解压。 -
1.2 Intellij Idea 安装离线插件
安装完成后,重新启动即可。
二、Lombok依赖配置
maven仓库查看版本并在pom.xml中添加依赖
org.projectlombok
lombok
1.18.12
provided
三、Java中使用Lombok
-
3.1 介绍
“样板”是一个术语,用于描述在应用程序的许多部分中很少改动就重复的代码。 对Java语言最常提出的批评之一是在大多数项目中都可以找到这种类型的代码。 这个问题通常是各种库中设计决策的结果,但由于语言本身的局限性而加剧了这一问题。 龙目岛计划(Project Lombok)旨在通过用简单的注释集代替某些最严重的违法者。
尽管使用批注来指示用法,实现绑定甚至生成框架使用的代码并不少见,但通常不会将其用于生成应用程序直接使用的代码。 部分原因是因为这样做需要在开发时急切地处理批注。 龙目岛项目就是这样做的。 通过集成到IDE中,Project Lombok能够注入可供开发人员立即使用的代码。 例如,仅将@Data批注添加到数据类中(如下所示),就会在IDE中产生许多新方法:
- 3.2 Lombok 注解
对于典型的Java项目来说,将数百行代码专门用于定义简单数据类所需的样板并不少见。 这些类通常包含许多字段,这些字段的getter和setter以及equals和hashCode实现。 在最简单的情况下,Project Lombok可以将这些类简化为必填字段和单个@Data批注。
当然,最简单的场景并不一定是开发人员每天面对的场景。 因此,Lombok项目中有许多注释,可以对类的结构和行为进行更精细的控制。
3.2.1 @Getter 和@Setter
@ Getter和@Setter批注分别为字段生成getter和setter。 正确生成的getter遵循布尔属性的约定,因此对于任何布尔字段foo而言,它都是isFoo getter方法名称而不是getFoo。 应当注意,如果带注释的字段所属的类包含与要生成的getter或setter同名的方法,则无论参数或返回类型如何,都不会生成相应的方法。
@Getter和@Setter注释均带有一个可选参数,以指定所生成方法的访问级别。
注解代码:
@Getter
@Setter
private boolean employed = true;
@Setter(AccessLevel.PROTECTED)
private String name;
等价于原Java代码:
private boolean employed = true;
private String name;
public boolean isEmployed() {
return employed;
}
public void setEmployed(final boolean employed) {
this.employed = employed;
}
protected void setName(final String name) {
this.name = name;
}
3.2.2 @NonNull
@NonNull批注用于指示需要对相应成员进行快速失败的空检查。 当放置在Lombok为其生成setter方法的字段上时,将生成null检查,如果提供null值,则将导致NullPointerException。 此外,如果Lombok正在为所属类生成构造函数,则该字段将添加到构造函数签名中,并且空检查将包含在生成的构造函数代码中。
此批注反映了在IntelliJ IDEA和FindBugs等中找到的@NotNull和@NonNull批注。 对于主题的这些变化,Lombok与注解无关。 如果Lombok遇到任何带有名称@NotNull或@NonNull的任何注解的成员,它将通过生成适当的相应代码来兑现它。 Lombok项目的作者进一步评论说,如果将这种类型的注解添加到Java中,则Lombok版本将被删除。
注解代码:
@Getter
@Setter
@NonNull
private List members;
等价于原Java代码:
@NonNull
private List members;
public Family(@NonNull final List members) {
if (members == null) throw new java.lang.NullPointerException("members");
this.members = members;
}
@NonNull
public List getMembers() {
return members;
}
public void setMembers(@NonNull final List members) {
if (members == null) throw new java.lang.NullPointerException("members");
this.members = members;
}
3.2.3 @ToString
该注释生成toString方法的实现。 默认情况下,所有非静态字段都将以名称/值对的形式包含在方法的输出中。 如果需要,可以通过将注解参数includeFieldNames设置为false来抑制在输出中包含属性名称。
通过将特定字段的字段名称包含在exclude参数中,可以从生成的方法的输出中排除特定字段。 或者,可以使用of参数来仅列出输出中所需的那些字段。 通过将callSuper参数设置为true,还可以包含超类的toString方法的输出。
注解代码:
@ToString(callSuper=true,exclude="someExcludedField")
public class Foo extends Bar {
private boolean someBoolean = true;
private String someStringField;
private float someExcludedField;
}
等价于原Java代码:
public class Foo extends Bar {
private boolean someBoolean = true;
private String someStringField;
private float someExcludedField;
@java.lang.Override
public java.lang.String toString() {
return "Foo(super=" + super.toString() +
", someBoolean=" + someBoolean +
", someStringField=" + someStringField + ")";
}
}
3.2.4 @EqualsAndHashCode
这个类级别的注释将使Lombok生成equals和hashCode方法,因为两者通过hashCode契约本质上联系在一起。 默认情况下,两种方法都将考虑类中任何非静态或瞬态的字段。 与@ToString非常相似,提供了exclude参数以防止将字段包含在生成的逻辑中。 也可以使用of参数来仅列出应考虑的那些字段。
就像@ToString一样,此注释也有一个callSuper参数。将其设置为true会导致equals通过在考虑当前类中的字段之前从超类调用equals来验证相等性。对于hashCode方法,它导致将超类的hashCode的结果并入哈希计算中。将callSuper设置为true时,请确保父类中的equals方法正确处理实例类型检查。如果父类检查该类是否具有特定类型,而不仅仅是两个对象的类相同,则可能导致不良结果。如果超类使用的是Lombok生成的equals方法,那么这不是问题。但是,其他实现可能无法正确处理此情况。还要注意,当类仅扩展Object时,无法将callSuper设置为true,因为这将导致实例相等性检查,从而使字段比较短路。这是由于生成的方法调用了Object的equals实现,如果正在比较的两个实例不是同一实例,则返回false。结果,在这种情况下,Lombok将生成编译时错误。
注解代码:
@EqualsAndHashCode(callSuper=true,exclude={"address","city","state","zip"})
public class Person extends SentientBeing {
enum Gender { Male, Female }
@NonNull private String name;
@NonNull private Gender gender;
private String ssn;
private String address;
private String city;
private String state;
private String zip;
}
等价于原Java代码:
public class Person extends SentientBeing {
enum Gender {
/*public static final*/ Male /* = new Gender() */,
/*public static final*/ Female /* = new Gender() */;
}
@NonNull
private String name;
@NonNull
private Gender gender;
private String ssn;
private String address;
private String city;
private String state;
private String zip;
@java.lang.Override
public boolean equals(final java.lang.Object o) {
if (o == this) return true;
if (o == null) return false;
if (o.getClass() != this.getClass()) return false;
if (!super.equals(o)) return false;
final Person other = (Person)o;
if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false;
if (this.gender == null ? other.gender != null : !this.gender.equals(other.gender)) return false;
if (this.ssn == null ? other.ssn != null : !this.ssn.equals(other.ssn)) return false;
return true;
}
@java.lang.Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = result * PRIME + super.hashCode();
result = result * PRIME + (this.name == null ? 0 : this.name.hashCode());
result = result * PRIME + (this.gender == null ? 0 : this.gender.hashCode());
result = result * PRIME + (this.ssn == null ? 0 : this.ssn.hashCode());
return result;
}
}
3.2.5 @Data
@Data批注可能是Project Lombok工具集中最常用的批注。 它结合了@ ToString,@ EqualsAndHashCode,@ Getter和@Setter的功能。 本质上,在类上使用@Data等同于使用默认的@ToString和@EqualsAndHashCode注释类以及使用@Getter和@Setter注释每个字段。 用@Data注释类也会触发Lombok的构造函数生成。 这将添加一个公共构造函数,该构造函数将任何@NonNull或final字段用作参数。 这提供了普通Java对象(POJO)所需的一切。
尽管@Data非常有用,但它不能提供与其他Lombok注释相同的控制粒度。 为了覆盖默认的方法生成行为,请使用其他Lombok批注之一对类,字段或方法进行批注,并指定必要的参数值以实现所需的效果。
@Data确实提供了可用于生成静态工厂方法的单个参数选项。 将staticConstructor参数的值设置为所需的方法名称将使Lombok将生成的构造函数设为私有,并公开具有给定名称的静态工厂方法。
注解代码:
@Data(staticConstructor="of")
public class Company {
private final Person founder;
private String name;
private List employees;
}
等价于原Java代码:
public class Company {
private final Person founder;
private String name;
private List employees;
private Company(final Person founder) {
this.founder = founder;
}
public static Company of(final Person founder) {
return new Company(founder);
}
public Person getFounder() {
return founder;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public List getEmployees() {
return employees;
}
public void setEmployees(final List employees) {
this.employees = employees;
}
@java.lang.Override
public boolean equals(final java.lang.Object o) {
if (o == this) return true;
if (o == null) return false;
if (o.getClass() != this.getClass()) return false;
final Company other = (Company)o;
if (this.founder == null ? other.founder != null : !this.founder.equals(other.founder)) return false;
if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false;
if (this.employees == null ? other.employees != null : !this.employees.equals(other.employees)) return false;
return true;
}
@java.lang.Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = result * PRIME + (this.founder == null ? 0 : this.founder.hashCode());
result = result * PRIME + (this.name == null ? 0 : this.name.hashCode());
result = result * PRIME + (this.employees == null ? 0 : this.employees.hashCode());
return result;
}
@java.lang.Override
public java.lang.String toString() {
return "Company(founder=" + founder + ", name=" + name + ", employees=" + employees + ")";
}
}
3.2.6 @Cleanup
@Cleanup批注可用于确保释放分配的资源。 当使用@Cleanup注释局部变量时,任何后续代码都将包装在try / finally块中,以确保在当前作用域的末尾调用cleanup方法。 默认情况下,@ Cleanup假定清除方法与输入和输出流一样被命名为“ close”。 但是,可以为注释的value参数提供不同的方法名称。 该注释只能使用不带参数的清除方法。
使用@Cleanup注释时,还需要注意一些注意事项。 如果cleanup方法引发异常,它将抢占方法主体中引发的所有异常。 这可能导致问题被掩埋的实际原因,在选择使用Project Lombok的资源管理时应予以考虑。 此外,随着Java 7中自动资源管理的兴起,这个特定的注释可能相对较短。
注解代码:
public void testCleanUp() {
try {
@Cleanup
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(new byte[] {'Y','e','s'});
System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
等价于原Java代码:
public void testCleanUp() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
baos.write(new byte[]{'Y', 'e', 's'});
System.out.println(baos.toString());
} finally {
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
3.2.6 @Synchronized
在方法上使用synchronized 关键字可能会导致不幸的后果,任何从事多线程软件开发的开发人员都可以证明。 如果是实例方法,则synchronized 关键字将锁定当前对象(此对象);对于静态方法,该关键字将锁定该类对象。 这意味着开发人员无法控制代码锁定同一对象,从而导致死锁。 通常建议改为显式地锁定在专用于该目的的单独对象上,并且不要以允许未经请求的锁定的方式公开。 为此,Project Lombok提供了@Synchronized批注。
用@Synchronized注释实例方法将提示Lombok生成一个名为$lock的私有锁定字段,该方法将在执行之前在该字段上锁定。 类似地,以相同的方式注释静态方法将生成一个名为$lock的私有静态对象,以供静态方法以相同方式使用。 可以通过为注释的value参数提供字段名称来指定其他锁定对象。 提供字段名称时,开发人员必须定义属性,因为Lombok不会生成该属性。
注解代码:
private DateFormat format = new SimpleDateFormat("MM-dd-YYYY");
@Synchronized
public String synchronizedFormat(Date date) {
return format.format(date);
}
等价于原Java代码:
private final java.lang.Object $lock = new java.lang.Object[0];
private DateFormat format = new SimpleDateFormat("MM-dd-YYYY");
public String synchronizedFormat(Date date) {
synchronized ($lock) {
return format.format(date);
}
}
3.2.8 @SneakyThrows
@SneakyThrows可能是批评者最多的Project Lombok批注,因为它是对已检查异常的直接攻击。 在使用检查异常方面存在很多分歧,许多开发人员认为这是一个失败的实验。 这些开发人员将喜欢@SneakyThrows。 处于已检查/未检查异常范围另一侧的那些开发人员最有可能将其视为隐藏潜在问题。
如果在throws子句中未列出IllegalAccessException或某些父类,则抛出IllegalAccessException通常会生成“未处理的异常”错误:
当使用@SneakyThrows注释时,错误消失了。
默认情况下,@ SneakyThrows将允许在不声明throws子句的情况下引发任何已检查的异常。 通过为注释的value参数提供可抛出的类(Class)数组,可以将其限制为特定的一组异常。
注解代码:
@SneakyThrows
public void testSneakyThrows() {
throw new IllegalAccessException();
}
等价于原Java代码:
public void testSneakyThrows() {
try {
throw new IllegalAccessException();
} catch (java.lang.Throwable $ex) {
throw lombok.Lombok.sneakyThrow($ex);
}
查看上面的代码和Lombok.sneakyThrow(Throwable)的签名,会使大多数人认为该异常已包装在RuntimeException中并重新抛出,但是事实并非如此。 scratchyThrow方法将永远不会正常返回,而是将提供的throwable完全不变。
- 3.3 优缺点
与任何技术选择一样,使用Lombok项目既有积极的影响,也有消极的影响。 将Lombok的注释合并到项目中可以大大减少在IDE中生成或手动编写的样板代码的行数。 这样可以减少维护开销,减少错误并增加可读性的类。
这并不是说在您的项目中使用Project Lombok注释没有不利之处。 Lombok项目主要旨在填补Java语言中的空白。 因此,可能会发生语言更改,从而无法使用Lombok的注释,例如添加了一流的属性支持。 此外,当与基于注释的对象关系映射(ORM)框架结合使用时,数据类上注释的数量可能开始变得笨拙。 这在很大程度上被Lombok注释所取代的代码量所抵消。 但是,那些避免频繁使用注释的人可能会选择其他方式。
四、实例
Example Code -LombokExample.zip
五、参考
Reducing Boilerplate Code with Project Lombok