0x01:Lombok简介
Lombok 是一款 Java开发插件使 Java 开发人员可以通过定义的一些注释来消除业务项目中冗长繁琐的代码,尤其是简单的代码 Java 模型对象(POJO)。在开发环境中使用 Lombok插件后,Java 开发人员可以节省重复构建,如 hashCode 和 equals 这种方法和各种业务对象模型 accessor 和 toString 大量的时间等待方法。对于这些方法,Lombok 它可以帮助我们在编译源代码期间自动生成这些方法,但不会像反射那样降低程序的性能。
0x02:安装Lombok
构建工具
- Gradle
在 build.gradle 文件中添加 Lombok 依赖:
dependencies { compileOnly 'org.projectlombok:lombok:1.18.10' annotationProcessor 'org.projectlombok:lombok:1.18.10'}
- Maven
在 Maven 项目的 pom.xml 文件中添加 Lombok 依赖:
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <scope>provided</scope></dependency>
- Ant
假设在 lib 已经存在于目录中 lombok.jar,然后设置 javac 任务:
<javac srcdir="src" destdir="build" source="1.8"> <classpath location="lib/lombok.jar" /></javac>
IDE
由于 Lombok 代码只在编译阶段生成,所以使用 Lombok 注释源代码,在 IDE 高亮显示错误可以通过安装来解决这个问题 IDE 解决相应的插件。此处不详细展开,具体安装方法可参考:
https://www.baeldung.com/lombok-ide
0x03:Lombok 详解
注:使用以下示例 Lombok 版本是 1.18.10
3.1 @Getter and @Setter
你可以使用 @Getter 或 @Setter 注释任何类别或字段,Lombok 默认会自动生成 getter/setter 方法。
- @Getter 注解
@Target({ElementType.FIELD, ElementType.TYPE})@Retention(RetentionPolicy.SOURCE)public @interface Getter { // 若getter方法不是public,可访问级别可设置 lombok.AccessLevel value() default lombok.AccessLevel.PUBLIC; AnyAnnotation[] onMethod() default {}; // 延迟初始化是否启用? boolean lazy() default false;}
- @Setter
@Target({ElementType.FIELD, ElementType.TYPE})@Retention(RetentionPolicy.SOURCE)public @interface Setter { // 若setter方法不是public,可访问级别可设置 lombok.AccessLevel value() default lombok.AccessLevel.PUBLIC; AnyAnnotation[] onMethod() default {}; AnyAnnotation[] onParam() default {};}
使用示例
@Getter@Setterpublic class GetterAndSetterDemo { String firstName; String lastName; LocalDate dateOfBirth;}
通过上述代码 Lombok 编译后,将生成以下代码:
public class GetterAndSetterDemo { String firstName; String lastName; LocalDate dateOfBirth; public GetterAndSetterDemo() { } // 省略其他setter和getter方法 public String getFirstName() { return this.firstName; } public void setFirstName(String firstName) { this.firstName = firstName; }}
- Lazy Getter
@Getter 支持一个注释 lazy 属性,默认为属性 false。当设置为 true 延迟初始化将在第一次调用时启用 getter 在初始化方法之前。
示例
public class LazyGetterDemo { public static void main(String[] args) { LazyGetterDemo m = new LazyGetterDemo(); System.out.println("Main instance is created"); m.getLazy(); } @Getter private final String notLazy = createValue("not lazy"); @Getter(lazy = true) private final String lazy = createValue("lazy"); private String createValue(String name) { System.out.println("createValue(" + name + ")"); return null; }}
通过上述代码 Lombok 编译后,将生成以下代码:
public class LazyGetterDemo { private final String notLazy = this.createValue("not lazy"); private final AtomicReference<Object> lazy = new AtomicReference(); // 部分代码已经省略 public String getNotLazy() { return this.notLazy; } public String getLazy() { Object value = this.lazy.get(); if (value == null) { synchronized(this.lazy) { value = this.lazy.get(); if (value == null) { String actualValue = this.createValue("lazy"); value = actualValue == null ? this.lazy : actualValue; this.lazy.set(value); } } } return (String)((String)(value == this.lazy ? null : value)); }}
通过上述代码可以知道调用 getLazy 如果发现了方法 value 为 null,初始化操作将在同步代码块中执行。
3.2 Constructor Annotations
- @NoArgsConstructor
使用 @NoArgsConstructor 注释可以作为指定类产生默认的构造函数,@NoArgsConstructor 注释的定义如下:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.SOURCE)public @interface NoArgsConstructor { // 若设置该属性,将生成staticName指定的私有结构函数和静态方法 String staticName() default ""; AnyAnnotation[] onConstructor() default {}; // 设置生成构造函数的访问级别,默认为public AccessLevel access() default lombok.AccessLevel.PUBLIC; // 如果设置为true,则所有final的初始化字段为0/null/false boolean force() default false;}
示例
@NoArgsConstructor(staticName = "getInstance")public class NoArgsConstructorDemo { private long id; private String name; private int age;}
通过上述代码 Lombok 编译后,将生成以下代码:
public class NoArgsConstructorDemo { private long id; private String name; private int age; private NoArgsConstructorDemo() { } public static NoArgsConstructorDemo getInstance() { return new NoArgsConstructorDemo(); }}
- @AllArgsConstructor
使用 @AllArgsConstructor 注释可以作为指定类生成包含所有成员的构造函数,@AllArgsConstructor 注释的定义如下:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.SOURCE)public @interface AllArgsConstructor { // 若设置该属性,将生成staticName指定的私有结构函数和静态方法 String staticName() default ""; AnyAnnotation[] onConstructor() default {}; // 设置生成构造函数的访问级别,默认为public AccessLevel access() default lombok.AccessLevel.PUBLIC;}
示例
@AllArgsConstructorpublic class AllArgsConstructorDemo { private long id; private String name; private int age;}
通过上述代码 Lombok 编译后,将生成以下代码:
public class AllArgsConstructorDemo { private long id; private String name; private int age; public AllArgsConstructorDemo(long id, String name, int age) { this.id = id; this.name = name; this.age = age; }}
- @RequiredArgsConstructor
使用 @RequiredArgsConstructor 注释可以是指定类必须初始化的成员变量 final 成员变量,产生相应的结构函数,@RequiredArgsConstructor 注释的定义如下:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.SOURCE)public @interface RequiredArgsConstructor { // 若设置该属性,将生成staticName指定的私有结构函数和静态方法 String staticName() default ""; AnyAnnotation[] onConstructor() default {}; // 设置生成构造函数的访问级别,默认为public AccessLevel access() default lombok.AccessLevel.PUBLIC;}
示例
@RequiredArgsConstructorpublic class RequiredArgsConstructorDemo { private final long id; private String name; private int age;}
通过上述代码 Lombok 编译后,将生成以下代码:
public class RequiredArgsConstructorDemo { private final long id; private String name; private int age; public RequiredArgsConstructorDemo(long id) { this.id = id; }}
3.3 @EqualsAndHashCode
使用 @EqualsAndHashCode 注释可以生成指定的类别 equals 和 hashCode 方法, @EqualsAndHashCode 注释的定义如下:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.SOURCE)public @interface EqualsAndHashCode { // 指定在生成的equals和hashCode方法中需要排除的字段列表 String[] exclude() default {}; // identity用于显式列出的字段,一般情况下non-static,non-identity将使用transient字段 String[] of() default {}; // 在计算执行字段之前,标识是否调用父类equals和hashcode方法? boolean callSuper() default false; boolean doNotUseGetters() default false; AnyAnnotation[] onParam() default {}; @Deprecated @Retention(RetentionPolicy.SOURCE) @Target({}) @interface AnyAnnotation {} @Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) public @interface Exclude {} @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) public @interface Include { String replaces() default ""; }}
示例
@EqualsAndHashCodepublic class EqualsAndHashCodeDemo { String firstName; String lastName; LocalDate dateOfBirth;}
通过上述代码 Lombok 编译后,将生成以下代码:
public class EqualsAndHashCodeDemo { String firstName; String lastName; LocalDate dateOfBirth; public EqualsAndHashCodeDemo() { } public boolean equals(Object o) { if (o == this) { return true; } else if (!(o instanceof EqualsAndHashCodeDemo)) { return false; } else { EqualsAndHashCodeDemo other = (EqualsAndHashCodeDemo)o; if (!other.canEqual(this)) { return false; } else { // 大量代码已经省略 } } public int hashCode() { int PRIME = true; int result = 1; Object $firstName = this.firstName; int result = result * 59 + ($firstName == null ? 43 : $firstName.hashCode()); Object $lastName = this.lastName; result = result * 59 + ($lastName == null ? 43 : $lastName.hashCode()); Object $dateOfBirth = this.dateOfBirth; result = result * 59 + ($dateOfBirth == null ? 43 : $dateOfBirth.hashCode()); return result; }}
3.4 @ToString
使用 @ToString 注释可以生成指定的类别 toString 方法, @ToString 注释的定义如下:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.SOURCE)public @interface ToString { // 打印输出时是否包含字段名称 boolean includeFieldNames() default true; // 列出打印输出时,字段列表需要排除 String[] exclude() default {}; // 显式列出需要打印输出的字段列表 String[] of() default {}; // 打印输出结果是否包含父类tostring方法的返回结果? boolean callSuper() default false; boolean doNotUseGetters() default false; boolean onlyExplicitlyIncluded() default false; @Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) public @interface Exclude {} @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) public @interface Include { int rank() default 0; String name() default ""; }}
示例
@ToString(exclude = {"dateOfBirth"})public class ToStringDemo { String firstName; String lastName; LocalDate dateOfBirth;}
通过上述代码 Lombok 编译后,将生成以下代码:
public class ToStringDemo { String firstName; String lastName; LocalDate dateOfBirth; public ToStringDemo() { } public String toString() { return "ToStringDemo(firstName=" + this.firstName + ", lastName=" + this.lastName + ")"; }}
3.5 @Data
@Data 注释的效果与同时使用以下注释的效果相同:
@ToString@Getter@Setter@RequiredArgsConstructor@EqualsAndHashCode
@Data 注释的定义如下:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.SOURCE)public @interface Data { String staticConstructor() default "";}
示例
@Datapublic class DataDemo { private Long id; private String summary; private String description;}
通过上述代码 Lombok 编译后,将生成以下代码:
public class DataDemo { private Long id; private String summary; private String description; public DataDemo() { } // setter和getter方法省略了summary和description的成员属性 public Long getId() { return this.id; } public void setId(Long id) { this.id = id; } public boolean equals(Object o) { if (o == this) { return true; } else if (!(o instanceof DataDemo)) { return false; } else { DataDemo other = (DataDemo)o; if (!other.canEqual(this)) { return false; } else { // 大量代码已经省略 } } } protected boolean canEqual(Object other) { return other instanceof DataDemo; } public int hashCode() { int PRIME = true; int result = 1; Object $id = this.getId(); int result = result * 59 + ($id == null ? 43 : $id.hashCode()); Object $summary = this.getSummary(); result = result * 59 + ($summary == null ? 43 : $summary.hashCode()); Object $description = this.getDescription(); result = result * 59 + ($description == null ? 43 : $description.hashCode()); return result; } public String toString() { return "DataDemo(id=" + this.getId() + ", summary=" + this.getSummary() + ", description=" + this.getDescription() + ")"; }}
3.6 @Log
若你将 @Log 将变体放在类上(适用于您使用的任何日志记录系统);之后,你会有一个静态的 final log 然后你可以使用这个字段来输出日志。
@Logprivate static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());@Log4jprivate static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);@Log4j2private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);@Slf4jprivate static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);@XSlf4jprivate static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);@CommonsLogprivate static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
3.7 @Synchronized
@Synchronized 更安全的变体是同步修饰符。与 synchronized 同样,注释只能应用于静态和实例方法。它的操作类似 synchronized 但它锁定在不同的对象上。synchronized 当关键字应用于实例方法时,锁定 this 类对象被锁定在静态方法中。对于 @Synchronized 对于注释声明的方法,它是锁定的 或者lock。@Synchronized 注释的定义如下:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Synchronized { // 指定锁定的字段名称 String value() default "";}
示例
public class SynchronizedDemo { private final Object readLock = new Object(); @Synchronized public static void hello() { System.out.println("world"); } @Synchronized public int answerToLife() { return 42; } @Synchronized("readLock") public void foo() { System.out.println("bar"); }}
通过上述代码 Lombok 编译后,将生成以下代码:
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 SynchronizedDemo() { } public static void hello() { synchronized($LOCK) { System.out.println("world"); } } public int answerToLife() { synchronized(this.$lock) { return 42; } } public void foo() { synchronized(this.readLock) { System.out.println("bar"); } }}
3.8 @Builder
使用 @Builder 注释可以实现指定类的建造者模式,可以放在类、构造函数或方法上。@Builder 注释的定义如下:
@Target({TYPE, METHOD, CONSTRUCTOR})@Retention(SOURCE)public @interface Builder { @Target(FIELD) @Retention(SOURCE) public @interface Default {} // 创建新builder实例的方法名称 String builderMethodName() default "builder"; // 创建Builder注解类对应实例的方法名称 String buildMethodName() default "build"; // builder类名称 String builderClassName() default ""; boolean toBuilder() default false; AccessLevel access() default lombok.AccessLevel.PUBLIC; @Target({FIELD, PARAMETER}) @Retention(SOURCE) public @interface ObtainVia { String field() default ""; String method() default ""; boolean isStatic() default false; }}
示例
@Builderpublic class BuilderDemo { private final String firstname; private final String lastname; private final String email;}
通过上述代码 Lombok 编译后,将生成以下代码:
public class BuilderDemo { private final String firstname; private final String lastname; private final String email; BuilderDemo(String firstname, String lastname, String email) { this.firstname = firstname; this.lastname = lastname; this.email = email; } public static BuilderDemo.BuilderDemoBuilder builder() { return new BuilderDemo.BuilderDemoBuilder(); } public static class BuilderDemoBuilder { private String firstname; private String lastname; private String email; BuilderDemoBuilder() { } public BuilderDemo.BuilderDemoBuilder firstname(String firstname) { this.firstname = firstname; return this; } public BuilderDemo.BuilderDemoBuilder lastname(String lastname) { this.lastname = lastname; return this; } public BuilderDemo.BuilderDemoBuilder email(String email) { this.email = email; return this; } public BuilderDemo build() { return new BuilderDemo(this.firstname, this.lastname, this.email); } public String toString() { return "BuilderDemo.BuilderDemoBuilder(firstname=" + this.firstname + ", lastname=" + this.lastname + ", email=" + this.email + ")"; } }}
3.9 @SneakyThrows
@SneakyThrows 注释用于自动抛出已检查的异常,不需要在方法中使用 throw 语句显式抛出。@SneakyThrows 注释的定义如下:
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})@Retention(RetentionPolicy.SOURCE)public @interface SneakyThrows { // 设置你想向上扔的异常类型 Class<? extends Throwable>[] value() default java.lang.Throwable.class;}
示例
public class SneakyThrowsDemo { @SneakyThrows @Override protected Object clone() { return super.clone(); }}
通过上述代码 Lombok 编译后,将生成以下代码:
public class SneakyThrowsDemo { public SneakyThrowsDemo() { } protected Object clone() { try { return super.clone(); } catch (Throwable var2) { throw var2; } }}
3.10 @NonNull
您可以使用方法或构建函数的参数 @NonNull 注意,它会自动为您生成非空校验句。@NonNull 注释的定义如下:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.TYPE_USE})@Retention(RetentionPolicy.CLASS)@Documentedpublic @interface NonNull {}
示例
public class NonNullDemo { @Getter @Setter @NonNull private String name;}
通过上述代码 Lombok 编译后,将生成以下代码:
public class NonNullDemo { @NonNull private String name; public NonNullDemo() { } @NonNull public String getName() { return this.name; } public void setName(@NonNull String name) { if (name == null) { throw new NullPointerException("name is marked non-null but is null"); } else { this.name = name; } }}
3.11 @Clean
@Clean 注释用于自动管理资源,在局部变量之前,在当前变量范围内完成退出之前,自动清理资源 try-finally 关闭流量的代码。
@Target(ElementType.LOCAL_VARIABLE)@Retention(RetentionPolicy.SOURCE)public @interface Cleanup { // 设置用于资源清理/回收的方法名称。相应的方法不能包含任何参数。默认名称为close。 String value() default "close";}
示例
public class CleanupDemo { public static void main(String[] args) throws IOException { @Cleanup InputStream in = new FileInputStream(args[0]); @Cleanup OutputStream out = new FileOutputStream(args[1]); byte[] b = new byte[10000]; while (true) { int r = in.read(b); if (r == -1) break; out.write(b, 0, r); } }}
通过上述代码 Lombok 编译后,将生成以下代码:
public class CleanupDemo { public CleanupDemo() { } public static void main(String[] args) throws IOException { FileInputStream in = new FileInputStream(args[0]); try { FileOutputStream out = new FileOutputStream(args[1]); try { byte[] b = new byte[10000]; while(true) { int r = in.read(b); if (r == -1) { return; } out.write(b, 0, r); } } finally { if (Collections.singletonList(out).get(0) != null) { out.close(); } } } finally { if (Collections.singletonList(in).get(0) != null) { in.close(); } } }}
3.11 @With
应用于类字段 @With 注释后,它将自动生成 withFieldName(newValue) 该方法将基于该方法 newValue 调用相应的构造函数,创建当前类对应的例子。@With 注释的定义如下:
@Target({ElementType.FIELD, ElementType.TYPE})@Retention(RetentionPolicy.SOURCE)public @interface With { AccessLevel value() default AccessLevel.PUBLIC; With.AnyAnnotation[] onMethod() default {}; With.AnyAnnotation[] onParam() default {}; @Deprecated @Retention(RetentionPolicy.SOURCE) @Target({}) public @interface AnyAnnotation { }}
示例
public class WithDemo { @With(AccessLevel.PROTECTED) @NonNull private final String name; @With private final int age; public WithDemo(String name, int age) { if (name == null) throw new NullPointerException(); this.name = name; this.age = age; }}
通过上述代码 Lombok 编译后,将生成以下代码:
public class WithDemo { @NonNull private final String name; private final int age; public WithDemo(String name, int age) { if (name == null) { throw new NullPointerException(); } else { this.name = name; this.age = age; } } protected WithDemo withName(@NonNull String name) { if (name == null) { throw new NullPointerException("name is marked non-null but is null"); } else { return this.name == name ? this : new WithDemo(name, this.age); } } public WithDemo withAge(int age) { return this.age == age ? this : new WithDemo(this.name, age); }}
3.12 其它特性
- val
val 在局部变量前使用相当于将变量声明为 final,此外 Lombok 类型推断将在编译过程中自动进行。val 用例:
public class ValExample { public String example() { val example = new ArrayList<String>(); example.add("Hello, World!"); val foo = example.get(0); return foo.toLowerCase(); } public void example2() { val map = new HashMap<Integer, String>(); map.put(0, "zero"); map.put(5, "five"); for (val entry : map.entrySet()) { System.out.printf("%d: %s\n", entry.getKey(), entry.getValue()); } }}
上述代码等价如下:
public class ValExample { public String example() { final ArrayList<String> example = new ArrayList<String>(); example.add("Hello, World!"); final String foo = example.get(0); return foo.toLowerCase(); } public void example2() { final HashMap<Integer, String> map = new HashMap<Integer, String>(); map.put(0, "zero"); map.put(5, "five"); for (final Map.Entry<Integer, String> entry : map.entrySet()) { System.out.printf("%d: %s\n", entry.getKey(), entry.getValue()); } }}
到目前为止,功能强大 Lombok 介绍完工具。