共计 12145 个字符,预计需要花费 31 分钟才能阅读完成。
相识
lombok 想必已经有很多人已经使用了很长时间了,而我却是第一次接触到,有点呆。lombok 主要是用于减少重复代码,通过一组简单的注释取代一些重复的 Java 代码。对于 lombok 的评价褒贬不一,有的人觉得特别方便,有的人觉得改变了一成不变的代码结构,增加了代码维护成本(有的人没有用过 lombok),我是觉得每一个工具诞生肯定是有他诞生的价值的,多学一个是一个啊,小老弟,用不用再说。:)
官方文档地址
官方 API 地址
官方注解介绍地址
准备
1、下载 idea 插件
我这里已经安装好了,没有安装的时候按钮应该是 Install
2、pom 文件引入 project lombok 的 maven 依赖
<dependency> | |
<groupId>org.projectlombok</groupId> | |
<artifactId>lombok</artifactId> | |
<version>1.18.2</version> | |
<scope>provided</scope> | |
</dependency> |
lombok maven 版本
注解介绍
@Getter 和 @Setter
顾名思义,生成 get 和 set 方法的注解,@Getter 和 @Setter 发生在编译阶段,编译之后,@Getter 和 @Setter 相关的注解就消失了,取而代之的是相应的 get 和 set 方法。
public class LombokTest { | |
@Getter @Setter | |
private boolean flag; | |
} |
lombok 遵循了 boolean 类型的 get 方法的约定,我们来看一下编译后的代码(等效 Java 代码)吧。
public class LombokTest { | |
private boolean flag; | |
public LombokTest() {} | |
public boolean isFlag() {return this.flag;} | |
public void setFlag(boolean flag) {this.flag = flag;} | |
} |
可以看到 boolean 类型的 get 方法是 isFlag()而不是 getFlag()。我们还可定义生成 get 和 set 方法的访问级别。
public class LombokTest {@Getter(AccessLevel.PUBLIC) | |
@Setter(AccessLevel.PROTECTED) | |
private String name; | |
} |
编译后的代码(等效 Java 代码)
public class LombokTest { | |
private String name; | |
public LombokTest() {} | |
public String getName() {return this.name;} | |
protected void setName(String name) {this.name = name;} | |
} |
可以看到 setName 的访问级别是 protected。lombok 提供了下面几种级别。
AccessLevel 枚举类的源代码。
public enum AccessLevel { | |
PUBLIC, // public | |
MODULE, // 编译后相当于 default | |
PROTECTED, // protected | |
PACKAGE, // default | |
PRIVATE, // private | |
NONE; // 不生成 | |
private AccessLevel() {} | |
} |
最后 @Getter 和 @Setter 注解是可以写在类级别的,作用于所有的成员变量,无法细粒度的控制访问级别。
@Getter(AccessLevel.MODULE) | |
@Setter(AccessLevel.PROTECTED) | |
public class LombokTest { | |
private String name; | |
private boolean flag; | |
} |
编译后的代码(等效 Java 代码)
public class LombokTest { | |
private String name; | |
private boolean flag; | |
public LombokTest() {} | |
String getName() {return this.name;} | |
boolean isFlag() {return this.flag;} | |
protected void setName(String name) {this.name = name;} | |
protected void setFlag(boolean flag) {this.flag = flag;} | |
} |
@NonNull
变量空检查,如果 @NonNull 注解使用在构造函数的字段时,构造函数也会进行空变量检查。不可用于类级别。
public class LombokTest { | |
@NonNull | |
@Setter | |
@Getter | |
private String name; | |
public LombokTest(@NonNull String name) {this.name = name;} | |
} |
编译后的代码(等效 Java 代码)
public class LombokTest { | |
@NonNull | |
private String name; | |
public LombokTest(@NonNull String name) {if (name == null) {throw new NullPointerException("name is marked @NonNull but is null"); | |
} else {this.name = name;} | |
} | |
public void setName(@NonNull String name) {if (name == null) {throw new NullPointerException("name is marked @NonNull but is null"); | |
} else {this.name = name;} | |
} | |
@NonNull | |
public String getName() {return this.name;} | |
} |
@ToString
用于生成 toString()方法。只能用于类级别。static 修饰的变量是不能生成在 toString()方法中的,原因是 static 修饰的变量在类创建的时候就生成了,只有一个;而普通的变量属于对象,每创建一个新的对象都会有一个。可以理解为 static 修饰的变量属于类,而普通变量数据对象,这样可能好理解一点。
@ToString | |
public class LombokTest { | |
private String name; | |
private static String phone; | |
} |
编译后的代码(等效 Java 代码)
public class LombokTest { | |
private String name; | |
private static String phone; | |
public LombokTest() {} | |
public String toString() {return "YmlProperties(name=" + this.name + ")"; | |
} | |
} |
默认情况下出了 static 修饰的字段都将以 name-value 的形式生成在 toString()方法中。同时 toString 注解中还可以设置一些属性来细粒度的控制 toString()方法。
下面是 ToString 注解的源码和解释:
@Target({ElementType.TYPE}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface ToString {boolean includeFieldNames() default true; // 生成 toString()方法时默认为 name-value 的形式,设置为 false 时 则 value 的形式。String[] exclude() default {}; // 不包含指定字段 | |
String[] of() default {}; // 包含指定字段 | |
boolean callSuper() default false; // 是否调用父类的 toString()方法 | |
boolean doNotUseGetters() default false; // 生成 toString()方法是否直接访问字段,设置为 true 时直接访问字段(如:this.name),false 时通过 getter()方法进行访问 (如:this.getName())。当然如果没有生成 getter() 方法,无论是否设置都直接访问字段 | |
boolean onlyExplicitlyIncluded() default false; // 是否仅仅包含添加了 @ToString.Include 的字段,默认为 false。@Target({ElementType.FIELD}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface Exclude {// 使用 @ToString.Exclude 修饰字段,需要和 @ToString 注解一起使用,单独使用无效。} | |
@Target({ElementType.FIELD, ElementType.METHOD}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface Include {// 使用 @ToString.Include 修饰字段,需要和 @ToString 注解一起使用,单独使用无效。int rank() default 0; | |
String name() default "";} | |
} |
注意:
- of 和 exclude 属性设置多个字段时,以字符串数组的形式,如:
@ToString(of = {"name","phone"}) // 包含 name 和 phone 字段 | |
@ToString(exclude = {"name","phone"}) // 不包含 name 和 phone 字段 |
- 使用 @ToString.Include 或者 @ToString.Exclude 注解时也需要使用 @ToString 注解,否者无效。
@ToString | |
public class LombokTest { | |
@ToString.Include | |
private String name; | |
@ToString.Exclude | |
private String phone; | |
} |
@EqualsAndHashCode
生成 equals()和 hashCode()方法。默认使用所有的非 static 和非 transient 字段生成这两个方法。
@EqualsAndHashCode | |
public class LombokTest { | |
private String name; | |
private String phone; | |
private boolean flag; | |
} |
编译后的代码(等效 Java 代码)
public class LombokTest { | |
private String name; | |
private String phone; | |
private boolean flag; | |
public LombokTest() {} | |
public boolean equals(Object o) {if (o == this) {return true;} else if (!(o instanceof LombokTest)) {return false;} else {LombokTest other = (LombokTest)o; | |
if (!other.canEqual(this)) {return false;} else { | |
label39: { | |
Object this$name = this.name; | |
Object other$name = other.name; | |
if (this$name == null) {if (other$name == null) {break label39;} | |
} else if (this$name.equals(other$name)) {break label39;} | |
return false; | |
} | |
Object this$phone = this.phone; | |
Object other$phone = other.phone; | |
if (this$phone == null) {if (other$phone != null) {return false;} | |
} else if (!this$phone.equals(other$phone)) {return false;} | |
if (this.flag != other.flag) {return false;} else {return true;} | |
} | |
} | |
} | |
protected boolean canEqual(Object other) {return other instanceof LombokTest;} | |
public int hashCode() { | |
int PRIME = true; | |
int result = 1; | |
Object $name = this.name; | |
int result = result * 59 + ($name == null ? 43 : $name.hashCode()); | |
Object $phone = this.phone; | |
result = result * 59 + ($phone == null ? 43 : $phone.hashCode()); | |
result = result * 59 + (this.flag ? 79 : 97); | |
return result; | |
} | |
} |
和 ToString 注解相似也可以通过设置一些属性来控制决堤生成的 hashCode()和 equals()方法。下面是 @EqualsAndHashCode 注解的源码和解释,和 @ToString 一样的属性就没有写注释了。
@Target({ElementType.TYPE}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface EqualsAndHashCode {String[] exclude() default {}; | |
String[] of() default {}; | |
boolean callSuper() default false;// 是否使用父类的 equals 和 toString 方法,注意:父类为 Object 时无法设置为 true。boolean doNotUseGetters() default false; | |
EqualsAndHashCode.AnyAnnotation[] onParam() default {}; | |
boolean onlyExplicitlyIncluded() default false; | |
/** @deprecated */ | |
@Deprecated | |
@Retention(RetentionPolicy.SOURCE) | |
@Target({}) | |
public @interface AnyAnnotation {// 过时的方法(deprecated)} | |
@Target({ElementType.FIELD}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface Exclude { } | |
@Target({ElementType.FIELD, ElementType.METHOD}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface Include {String replaces() default ""; | |
} | |
} |
@Data
这是 lombok 中使用的最多的注解,相当于 @ToString
,@EqualsAndHashCode
,@RequiredArgsConstructor
,@Getter
和@Setter
五个注解的功能。虽然使用起来方法,但是失去了细粒度的控制。如果需要细粒度控制,则需要进行注解的覆盖。
@Data | |
public class LombokTest { | |
private String name; | |
private String phone; | |
private boolean flag; | |
} |
编译后的代码(等效 Java 代码)
public class LombokTest { | |
private String name; | |
private String phone; | |
private boolean flag; | |
public LombokTest() {} | |
public String getName() {return this.name;} | |
public String getPhone() {return this.phone;} | |
public boolean isFlag() {return this.flag;} | |
public void setName(String name) {this.name = name;} | |
public void setPhone(String phone) {this.phone = phone;} | |
public void setFlag(boolean flag) {this.flag = flag;} | |
public boolean equals(Object o) {if (o == this) {return true;} else if (!(o instanceof LombokTest)) {return false;} else {LombokTest other = (LombokTest)o; | |
if (!other.canEqual(this)) {return false;} else { | |
label39: {Object this$name = this.getName(); | |
Object other$name = other.getName(); | |
if (this$name == null) {if (other$name == null) {break label39;} | |
} else if (this$name.equals(other$name)) {break label39;} | |
return false; | |
} | |
Object this$phone = this.getPhone(); | |
Object other$phone = other.getPhone(); | |
if (this$phone == null) {if (other$phone != null) {return false;} | |
} else if (!this$phone.equals(other$phone)) {return false;} | |
if (this.isFlag() != other.isFlag()) {return false;} else {return true;} | |
} | |
} | |
} | |
protected boolean canEqual(Object other) {return other instanceof LombokTest;} | |
public int hashCode() { | |
int PRIME = true; | |
int result = 1; | |
Object $name = this.getName(); | |
int result = result * 59 + ($name == null ? 43 : $name.hashCode()); | |
Object $phone = this.getPhone(); | |
result = result * 59 + ($phone == null ? 43 : $phone.hashCode()); | |
result = result * 59 + (this.isFlag() ? 79 : 97); | |
return result; | |
} | |
public String toString() {return "LombokTest(name=" + this.getName() + ", phone=" + this.getPhone() + ", flag=" + this.isFlag() + ")"; | |
} | |
} |
下面看一下 @Data 注解的源码。
@Target({ElementType.TYPE}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface Data {String staticConstructor() default ""; | |
} |
可以看到提供了一个,staticConstructor(), 这个方法的作用是私有化构造器,并生成一个静态的获取实例的方法,这样就可通过类名来获取实例(如:LombokTest.getLombokTest())
@Data(staticConstructor = "getLombokTest")
private LombokTest() {} | |
public static LombokTest getLombokTest() {return new LombokTest(); | |
} | |
// ... 省略其他代码 |
@Cleanup
自动释放资源,主要用于 IO 流的操作,不需要手动编写释放资源的 try/finally
代码块。
public class LombokTest {public static void main(String[] args) throws Exception {@Cleanup InputStream is = new FileInputStream(args[0]); | |
@Cleanup OutputStream os = new FileOutputStream(args[0]); | |
} | |
} |
编译后的代码(等效 Java 代码)
public class LombokTest {public static void main(String[] args) throws Exception {FileInputStream is = new FileInputStream(args[0]); | |
try {OutputStream os = new FileOutputStream(args[0]); | |
if (Collections.singletonList(os).get(0) != null) {os.close(); | |
} | |
} finally {if (Collections.singletonList(is).get(0) != null) {is.close(); | |
} | |
} | |
} | |
} |
还是挺方便的。再来看看 @Cleanup 注解的源代码
@Target({ElementType.LOCAL_VARIABLE}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface Cleanup {String value() default "close"; | |
} |
可以看到默认调用的是 close 方法,也就是说我们可以设置 finally 方法体具体调用的流处理方法。如:
public static void main(String[] args) throws Exception {@Cleanup(value = "available") InputStream is = new FileInputStream(args[0]); | |
@Cleanup OutputStream os = new FileOutputStream(args[0]); | |
} |
编译后的代码(等效 Java 代码)
具体可以指定哪些字段,需要流对象支持哪些方法了。如 InputStream:
@Synchronized
同步代码块,只能使用在类方法(static)或者对象方法(普通方法)上,synchronized 关键字锁住的是 this,@Synchronized 注解默认使用的锁为 $lock, 静态的锁为 $LOCK,也可以自己指定锁对象,如示例中的:world
public class LombokTest { | |
@Synchronized | |
public void hello() {} | |
@Synchronized | |
public static void helloWorld() {} | |
private final Object world = new Object(); | |
@Synchronized("world") | |
public void world() {} | |
} |
编译后的代码(等效 Java 代码)
public class LombokTest {private final Object $lock = new Object[0]; | |
private static final Object $LOCK = new Object[0]; | |
private final Object world = new Object(); | |
public LombokTest() {} | |
public void hello() { | |
Object var1 = this.$lock; | |
synchronized(this.$lock) {;} | |
} | |
public static void helloWorld() { | |
Object var0 = $LOCK; | |
synchronized($LOCK) {;} | |
} | |
public void world() { | |
Object var1 = this.world; | |
synchronized(this.world) {;} | |
} | |
} |
@SneakyThrows
个人觉得没有啥意义,唯一的作用可能就是不用手动捕获异常或者向上抛出,而是通过 lombok 帮你捕获。。。可以指定捕获的异常类型,默认捕获的是 Throwable。
public class LombokTest {@SneakyThrows() | |
public void testSneakyThrows() {throw new IllegalAccessException(); | |
} | |
@SneakyThrows(IllegalAccessException.class) | |
public void testSneakyThrows2() {throw new IllegalAccessException(); | |
} | |
} |
编译后的代码(等效 Java 代码)
public class LombokTest {public LombokTest() { } | |
public void testSneakyThrows() { | |
try {throw new IllegalAccessException(); | |
} catch (Throwable var2) {throw var2;} | |
} | |
public void testSneakyThrows2() { | |
try {throw new IllegalAccessException(); | |
} catch (IllegalAccessException var2) {throw var2;} | |
} | |
} |
感觉有点呆。。。官方建议在没有深思熟虑之前不要使用这个注解。。。下面是官网上的一句话:
- You can pass any number of exceptions to the
@SneakyThrows
annotation. If you pass no exceptions, you may throw any exception sneakily.
@NoArgsConstructor,@ RequiredArgsConstructor,@ AllArgsConstructor
@NoArgsConstructor 生成一个无参构造器。
@NoArgsConstructor 注解源代码,另外两个注解的源码和这个很类似,就不贴出来了。
@Target({ElementType.TYPE}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface NoArgsConstructor {String staticName() default ""; // 如果指定了相应字段,则私有化构造并生成一个静态的获取实例方法。NoArgsConstructor.AnyAnnotation[] onConstructor() default {}; | |
AccessLevel access() default AccessLevel.PUBLIC; | |
boolean force() default false; | |
/** @deprecated */ | |
@Deprecated | |
@Retention(RetentionPolicy.SOURCE) | |
@Target({}) | |
public @interface AnyAnnotation {// 过时的(deprecated)} | |
} |
@ RequiredArgsConstructor 生成包含必须处理的字段的构造器,如:final 修饰(必须初始化)、@NonNull 注解修饰的。
@RequiredArgsConstructor | |
public class LombokTest { | |
private String name; | |
private String phone; | |
private transient boolean flag; | |
private final String finalFiled; | |
private static int num; | |
} |
编译后的代码(等效 Java 代码)
public class LombokTest { | |
private String name; | |
private String phone; | |
private transient boolean flag; | |
private final String finalFiled; | |
private static int num; | |
public LombokTest(String finalFiled) {this.finalFiled = finalFiled;} | |
} |
@ AllArgsConstructor 生成一个全参构造器,除 static 修饰的字段。@NonNull 可以组合使用。
@AllArgsConstructor | |
public class LombokTest { | |
private String name; | |
@NonNull | |
private String phone; | |
private transient boolean flag; | |
private final String finalFiled; | |
private static int num; | |
} |
编译后的代码(等效 Java 代码)
public class LombokTest { | |
private String name; | |
@NonNull | |
private String phone; | |
private transient boolean flag; | |
private final String finalFiled; | |
private static int num; | |
public LombokTest(String name, @NonNull String phone, boolean flag, String finalFiled) {if (phone == null) {throw new NullPointerException("phone is marked @NonNull but is null"); | |
} else { | |
this.name = name; | |
this.phone = phone; | |
this.flag = flag; | |
this.finalFiled = finalFiled; | |
} | |
} | |
} |
小结
学到这里常用的 Lombok 注解基本上都有了,如果想要根据深入的学习,建议去官网看。官网地址文章开始已经列出来了,加油!!!