Lombok是一个通过注解的形式或简单关键字简化和消除Java应用程序中一些必须但是重复或显得臃肿的样板代码的实用工具,使用Lombok会在编译阶段根据相应的注解生成对应的字节码,使编译前的源码看起来更加简洁,但功能不变。
使用前导包如下
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
常用注解如下:
一、@val / var
val用来简化局部变量声明的类型,与Java10中的var关键字类似,都是从初始化表达式中推断出变量的声明类型,起到本地类型推断的作用。需要注意的是val修饰的变量都会变成final类型,其引用不可更改。
val example = new ArrayList<String>();
example.add("hello");
example.add("lombok");
val element = example.get(0);
等价于
final ArrayList<String> example = new ArrayList<String>();
example.add("hello");
example.add("lombok");
final String element = example.get(0);
注意:
1.只能在本地变量声明的时候使用,不可在类的字段上使用
2.val修饰的变量本身是final类型的,不能被修改
二、@NonNull
常用于加在方法和构造函数的入参上,它会帮助我们生成检查NullPointerException的代码
public NonNullExample(@NonNull Person person) {
this.name = person.getName();
}
等价于
public NonNullExample(@NonNull Person person) {
if(person == null) {
throw new NullPointException("person");
}
this.name = person.getName();
}
三、@Cleanup
用来简化资源清理回收的代码,确保指定的资源在退出当前代码执行范围前进行自动清理,消除常见的try-catch-finally代码样板,作用等同于try-with-resource,不过需要注意@Cleanup只能指定没有参数的资源销毁方法,如果销毁方法有入参则不能使用@Cleanup注解
public static void tradition() {
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream("test.txt");
out = new FileOutputStream("output.txt");
byte[] buffer = new byte[1024];
int begin = 0;
while (true) {
int len = in.read(buffer);
if (len == -1)
break;
out.write(buffer, begin, len);
begin += len;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void tryWithResource() {
try (InputStream in = new FileInputStream("test.txt");
OutputStream out = new FileOutputStream("output.txt")) {
byte[] buffer = new byte[1024];
int begin = 0;
while (true) {
int len = in.read(buffer);
if (len == -1)
break;
out.write(buffer, begin, len);
begin += len;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void cleanUp() {
try {
@Cleanup InputStream in = new FileInputStream("test.txt");
@Cleanup OutputStream out = new FileOutputStream("output.txt");
byte[] buffer = new byte[1024];
int begin = 0;
while (true) {
int len = in.read(buffer);
if (len == -1)
break;
out.write(buffer, begin, len);
begin += len;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
四、@Getter / @Setter
分别用来简化getter和setter样板代码,默认生成的getter、setter方法修饰符为public,如果需要指定方法的访问范围,可以设置AccessLevel属性,如:
@Getter
@Setter(AccessLevel.PROTECTED)
private String password;
另外,@Getter注解还有一个lazy=true的属性,设置了该属性会使我们调用getter方法时才真正去计算获取到的值,并且将第一次计算后的结果缓存下来,之后的调用直接返回该缓存值
@Getter(lazy = true)
private final double[] cached = expensive();
private double[] expensive() {
long begin = System.currentTimeMillis();
double[] result = new double[5];
for (int i = 0; i < result.length; i++) {
result[i] = Math.asin(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println((System.currentTimeMillis() - begin) / 1000);
return result;
}
public static void main(String[] args) {
GetterLazyExample example = new GetterLazyExample();
System.out.println(example.getCached());
System.out.println(example.getCached());
}
等价于
private final AtomicReference<Object> cached = new AtomicReference<>();
public double[] getCached() {
Object value = this.cached.get();
if (value == null) {
synchronized (this.cached) {
value = this.cached.get();
if (value == null) {
final double[] actualValue = expensive();
value = actualValue == null ? this.cached : actualValue;
this.cached.set(value);
}
}
}
return (double[]) (value == this.cached ? null : value);
}
private double[] expensive() {
long begin = System.currentTimeMillis();
double[] result = new double[5];
for (int i = 0; i < result.length; i++) {
result[i] = Math.asin(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println((System.currentTimeMillis() - begin) / 1000);
return result;
}
public static void main(String[] args) {
GetterLazyExample_Src example = new GetterLazyExample_Src();
System.out.println(example.getCached());
System.out.println(example.getCached());
}
五、@ToString
用来自动生成toString方法,默认的toString方法会打印出类名和字段属性和值,如果需要排除指定字段可以用exclude='字段名’的方式进行排除;如果要嵌套调用父类的toString方法,则加上callSuper=true,includeFieldNames=true等属性
// @ToString // 默认打印类名、每个字段名=值,用逗号分隔
// @ToString(exclude="password") //exclude属性指定排除哪些字段
@ToString(callSuper = true,includeFieldNames=true)
public class ToStringExample extends Parent {
@Getter
@Setter
private String name;
@Getter
@Setter
private String password;
@Getter
@Setter
private int age;
public static void main(String[] args) {
System.out.println(new ToStringExample());
}
}
@ToString
class Parent {
@Getter
@Setter
private String address;
@Getter
@Setter
private String city;
}
六、@EqualsAndHashCode
用来从字段中自动生成equals和hashCode方法,默认情况下使用的是所有非静态字段,也可以使用exclude属性排除指定的字段
@EqualsAndHashCode(exclude= {"name"})
public class EqualsAndHashCodeExample {
@Getter
@Setter
private String name;
@Getter
@Setter
private int age;
@Getter
@Setter
private double weight;
public static void main(String[] args) {
EqualsAndHashCodeExample example1 = new EqualsAndHashCodeExample();
example1.setName("小明");
example1.setAge(10);
EqualsAndHashCodeExample example2 = new EqualsAndHashCodeExample();
example2.setName("小红");
example2.setAge(10);
System.out.println(example1.hashCode());
System.out.println(example2.hashCode());
System.out.println(example1.equals(example2));
}
}
七、@NoArgsConstructor
用来生成无参构造函数,如果类含有final字段,会出现编译错误,通过指定属性force为true,为final字段进行初始化
@NoArgsConstructor
public class NoArgsConstructorExample {
@Getter
@Setter
private String name;
}
等价于
public class NoArgsConstructorExample {
private String name;
public NoArgsConstructorExample() {
//public无参构造器
}
//省略getter、setter方法
......
}
八、@RequiredArgsConstructor
用来生成包含所有修饰为@NonNull的成员属性的构造函数
@RequiredArgsConstructor
public class RequiredArgsConstructorExample {
@Getter
@Setter
@NonNull
private String name;
@Getter
@Setter
private String password;
@Getter
@Setter
@NonNull
private Character sex;
}
等价于
public class RequiredArgsConstructorExample {
private String name;
private String password;
private Character sex;
private RequiredArgsConstructorExample(String name, Character sex) {
if(name == null) {
throw new NullPointerException("name");
}
if(sex == null) {
throw new NullPointerException("sex");
}
this.name = name;
this.sex = sex;
}
//省略getter、setter方法
......
}
九、@AllArgsConstructor
@AllArgsConstructor
public class AllArgsContructorExample {
@Getter
@Setter
private String name;
@Getter
@Setter
private Integer age;
@Getter
@Setter
private String address;
}
等价于
public class AllArgsContructorExample {
private String name;
private Integer age;
private String address;
public AllArgsContructorExample(String name, Integer age, String address) {
this.name = name,
this.age = age;
this.address = address;
}
//省略getter、setter方法
......
}
十、@Data
是一个简单粗暴的组合注解,使用@Data注解相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstructor这几个注解
@Data
public class DataExample {
private String name;
private int age;
private String password;
}
十一、@Value
跟@Data类似,区别在于如果变量不加@NonFinal修饰,@Value会将字段变成final类型,同时也没有setter方法
十二、@NonFinal
修饰字段,用来取消因使用@FieldDefaults和@Value而加上的final修饰符
@Value
public class NonFinalExample {
private String id; //final
private String name; //final
@NonFinal
private String password; //非final
}
十三、@Builder
简化了普通的建造者模式API,可以用在类、构造器、方法上,如果字段属于集合类型,加上@Singular,会生成两个向集合中添加单一元素和所有元素的方法,以及一个清除集合的方法
@Builder
public class Example {
private int foo;
private final String bar;
}
等价于
public class Example<T> {
private T foo;
private final String bar;
private Example(T foo, String bar) {
this.foo = foo;
this.bar = bar;
}
public static <T> ExampleBuilder<T> builder() {
return new ExampleBuilder<T>();
}
public static class ExampleBuilder<T> {
private T foo;
private String bar;
private ExampleBuilder() {}
public ExampleBuilder foo(T foo) {
this.foo = foo;
return this;
}
public ExampleBuilder bar(String bar) {
this.bar = bar;
return this;
}
@java.lang.Override
public String toString() {
return "ExampleBuilder(foo = " + foo + ", bar = " + bar + ")";
}
public Example build() {
return new Example(foo, bar);
}
}
}
十四、@Tolerate
该注解用来解决某些情况下使用Lombok注解生成的构造器或方法与开发者自己写构造器或方法因为冲突而被跳过的情况,将@Tolerate修饰在构造器/方法上,会被lombok视为该构造器/方法不存在,典型的如当@Data和@Builder同时使用时Lombok生成构造器只有一个包含所有成员属性的构造函数,如果再自定义一个无参构造函数将会冲突,此时可以使用@Tolerate解决
@Data
@Builder
public class TolerateExample {
private String name;
private String age;
@Tolerate
public TolerateExample() {
}
}
分析一:
单独使用@Data注解,编译后的类文件会生成无参数构造方法(这并不是@Data提供的特性,而是Java自带的特性)
单独使用@Builder注解,发现生成了全属性的构造方法
@Data和@Builder一起用时候,则没有生成默认的构造方法。如果手动添加无参数构造方法或者用@NoArgsConstructor注解都会报错,这时候就需要手动添加该无参构造方法,且在方法前添加@Tolerate即可,这样就能生成无参构造了
分析二:
为什么只有一个整体的 @EqualsAndHashCode 注解,而不是分开的两个 @Equals 和 @HashCode?
在 Java 中有规定,当两个对象 equals 时,他们的 hashcode 一定要相同,反之,当 hashcode 相同时,对象不一定 equals。所以 equals 和 hashcode 要一起实现,免得发生违反 Java 规定的情形发生
分析三:
@RequiredArgsConstructor : 生成一个包含 “特定参数” 的构造器,特定参数指的是那些有加上 final 修饰词的变量
补充一下,如果所有的变量都是正常的,都没有用 final 修饰的话,那就会生成一个没有参数的构造器
分析四:
@Data整合包,只要加了 @Data 这个注解,等于同时加了以下注解
@Getter/@Setter
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor
分析五:
@Value也是整合包,但是他会把所有的变量都设成 final 的,其他的就跟 @Data 一样,等于同时加了以下注解
@Getter (注意没有setter)
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor
上面那个 @Data 适合用在 POJO 或 DTO 上,而这个 @Value 注解,则是适合加在值不希望被改变的类上,像是某个类的值当创建后就不希望被更改,只希望我们读它而已,就适合加上 @Value 注解,也就是 @Value for immutable class
另外注意一下,此 lombok 的注解 @Value 和另一个 Spring 的注解 @Value 撞名,在 import 时不要 import 错了
分析五:
@Slf4j自动生成该类的 log 静态常量,要打日志就可以直接打,不用再手动 new log 静态常量了
除了 @Slf4j 之外,lombok 也提供其他日志框架的变种注解可以用,像是 @Log、@Log4j…等,他们都是帮我们创建一个静态常量 log,只是使用的库不一样而已
@Log //对应的log语句如下
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
@Log4j //对应的log语句如下
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
SpringBoot默认支持的就是 slf4j + logback 的日志框架,所以也不用再多做啥设定,直接就可以用在 SpringBoot project上,log 系列注解最常用的就是 @Slf4j