Lombok——Java开发常用的代码生成工具。通过使用注解,在编译期将注解替换为相应代码

配置
POM依赖如下
| 12
 3
 4
 5
 
 | <dependency><groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 <version>1.18.24</version>
 </dependency>
 
 | 
然后,在IDEA的Plugins Marketplace 中搜索安装Lombok插件。最后,在IDEA中使能注解处理器功能即可

常用注解
@Data 注解
放置在类上,为该类的所有属性添加get、set方法,为该类添加equals、canEquals、hashCode、toString方法。示例源码如下:
| 12
 3
 4
 5
 6
 7
 8
 
 | @Data
 class Pc1 implements Serializable{
 private static final long serialVersionUID = 1;
 private int id;
 private double prcie;
 private String pcname;
 }
 
 | 
编译后class文件如下,可以看到提供了上述提到的所有方法,并且源码依然很简洁

@Getter、@Setter 注解
放置在类或属性上,为该类下全部属性或特定属性添加Get/Set方法。示例源码如下:
| 12
 3
 4
 5
 6
 7
 8
 
 | class Pc2 implements Serializable{private static final long serialVersionUID = 1;
 private int id;
 @Setter
 private double prcie;
 @Getter
 private String pcname;
 }
 
 | 
编译后class文件如下,可以看到字段上添加了相应的方法

@Accessors 注解
该注解会对通过Lombok生成的get、set方法进行调整,故使用该注解的同时,需要保证已经添加了 @Data 或 @Getter/@Setter注解。具体地:
- 设置@Accessors注解的chain属性为true时,则属性的setter方法返回this而不是void
- 设置@Accessors注解的fluent属性为true时,则setter、getter的方法名称不会以set、get作为前缀,而是直接使用字段名作为方法名。同时,当fluent属性为true时,chain属性默认为true,故setter方法也会返回this,而不是void
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 
 | import lombok.*;import lombok.experimental.Accessors;
 
 public class AccessorsTest {
 
 public static void main(String[] args) {
 Cat cat = new Cat();
 cat.setAge(18)
 .setName("Tom");
 String catName = cat.getName();
 Integer catAge = cat.getAge();
 System.out.println("cat name: " + catName);
 System.out.println("cat age: " + catAge);
 System.out.println("cat: " + cat.toString());
 
 System.out.println("-------------------------------------------- ");
 
 Pig pig = new Pig();
 pig.name("Bob")
 .age(24);
 String pigName = pig.name();
 Integer pigAge = pig.age();
 System.out.println("pig name: " + pigName);
 System.out.println("pig age: " + pigAge);
 System.out.println("Pig: " + pig.toString());
 }
 }
 
 @Data
 
 @Accessors(chain = true)
 class Cat {
 private String name;
 
 private Integer age;
 }
 
 @Getter
 @Setter
 @ToString
 
 
 @Accessors(fluent = true)
 class Pig {
 private String name;
 
 private Integer age;
 }
 
 | 

@EqualsAndHashCode 注解
为该类添加equals、canEqual、hashCode方法。示例源码如下:
| 12
 3
 4
 5
 6
 7
 
 | @EqualsAndHashCodeclass Pc5 implements Serializable {
 private static final long serialVersionUID = 1;
 private int id;
 private double prcie;
 private String pcname;
 }
 
 | 
编译后class文件如下,该类添加了equals、canEqual、hashCode方法

@ToString 注解
放置在类上,为该类添加toString方法。示例源码如下
| 12
 3
 4
 5
 6
 
 | @ToStringclass Pc6 {
 private int id;
 private double price;
 private String pcname;
 }
 
 | 
编译后class文件如下,该类添加了toString方法

callSuper 属性
对于子类而言,如果我们期望让子类的equals、hashCode、toString方法包含父类的属性,需要将子类上的@EqualsAndHashCode、@ToString注解的callSuper属性设置为True
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 
 | import lombok.*;
 public class CallSuperTest {
 public static void main(String[] args) {
 BigDog bigDog1 = new BigDog(1);
 bigDog1.setName("Bob");
 bigDog1.setType("big");
 
 BigDog bigDog2 = new BigDog(1);
 bigDog2.setName("Tony");
 bigDog2.setType("big");
 
 System.out.println("<Big Dog>: equals: " + bigDog1.equals(bigDog2));
 System.out.println("<Big Dog>: hashcode 1: " + bigDog1.hashCode());
 System.out.println("<Big Dog>: hashcode 2: " + bigDog2.hashCode());
 System.out.println("<Big Dog>: toString 1: " + bigDog1.toString());
 System.out.println("<Big Dog>: toString 2: " + bigDog2.toString());
 
 SmallDog smallDog1 = new SmallDog(1);
 smallDog1.setName("Bob");
 smallDog1.setType("small");
 
 SmallDog smallDog2 = new SmallDog(1);
 smallDog2.setName("tony");
 smallDog2.setType("small");
 
 System.out.println("--------------------------------------------");
 System.out.println("<Small Dog>: equals: " + smallDog1.equals(smallDog2));
 System.out.println("<Small Dog>: hashcode 1: " + smallDog1.hashCode());
 System.out.println("<Small Dog>: hashcode 2: " + smallDog2.hashCode());
 System.out.println("<Small Dog>: toString 1: " + smallDog1.toString());
 System.out.println("<Small Dog>: toString 2: " + smallDog2.toString());
 
 }
 }
 
 @AllArgsConstructor
 @NoArgsConstructor
 @Data
 class Dog {
 private String name;
 
 private String type;
 }
 
 @AllArgsConstructor
 @EqualsAndHashCode
 @ToString
 class BigDog extends Dog {
 private Integer id;
 }
 
 @AllArgsConstructor
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
 class SmallDog extends Dog {
 private Integer id;
 }
 
 | 
效果如下所示

@AllArgsConstructor、@NoArgsConstructor 注解
@AllArgsConstructor注解为该类添加一个全参构造器。需要注意的是:由于此时已经有构造器了,Java不再提供无参构造器。示例源码如下
| 12
 3
 4
 5
 6
 7
 
 | @AllArgsConstructorclass Pc3 implements Serializable{
 private static final long serialVersionUID = 1;
 private int id;
 private double prcie;
 private String pcname;
 }
 
 | 
编译后class文件如下,该类添加了一个全参构造器

@NoArgsConstructor注解为该类添加一个无参构造器。示例源码如下:
| 12
 3
 4
 5
 6
 7
 
 | @NoArgsConstructorclass Pc4 implements Serializable {
 private static final long serialVersionUID = 1;
 private int id;
 private double prcie;
 private String pcname;
 }
 
 | 
编译后class文件如下,该类添加了一个无参构造器

@Builder、@SuperBuilder 注解
@Builder为类提供了基于建造者模式的创建对象的方法
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 
 | import lombok.*;
 public class BuilderTest {
 public static void main(String[] args) {
 Animal animal = Animal.builder()
 .id(996)
 .type("dog")
 .remark("this is a dog")
 .build();
 System.out.println("animal: " + animal);
 
 Animal animal3 = new Animal();
 animal3.setId(3);
 System.out.println("animal3: " + animal3);
 
 Animal animal2 = Animal.builder()
 .id(2)
 .build();
 System.out.println("animal2: " + animal2);
 }
 }
 
 @Builder
 @AllArgsConstructor
 @NoArgsConstructor
 @Data
 class Animal {
 private Integer id;
 
 private String type = "CAT";
 
 
 @Builder.Default
 private String remark = "test";
 }
 
 | 
效果如下所示。特别地,type属性虽然设置了默认值,但通过build方式创建对象时,如果没有设置该值,则其不会使用默认值。故可在有默认值的属性上添加 @Builder.Default 来解决该问题

但对于子类而言,添加@Builder注解后,如果通过build方式创建子类对象时,无法设置父类属性值,只能设置子类自己的属性。故对于该问题,Lombok自1.18.2版本开始引入了@SuperBuilder注解。只需在父类、子类同时使用该注解即可
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 
 | import lombok.*;import lombok.experimental.SuperBuilder;
 
 public class SuperBuildTest {
 public static void main(String[] args) {
 People people = People.builder()
 .firstName("Bob")
 .lastName("Wang")
 .build();
 System.out.println("people: " + people);
 
 Man man = Man.builder()
 .firstName("Tony")
 .lastName("Li")
 .age(18)
 .weight(59.9)
 .sex("man")
 .build();
 System.out.println("man: " + man);
 
 Man man2 = Man.builder()
 .firstName("Tom")
 .age(18)
 .build();
 System.out.println("man2 :" + man2);
 
 }
 }
 
 @ToString
 @SuperBuilder
 class People {
 private String firstName;
 
 private String lastName;
 }
 
 @ToString(callSuper = true)
 @SuperBuilder
 class Man extends People {
 private Integer age;
 
 private Double weight;
 
 @Builder.Default
 private String sex = "男人";
 }
 
 | 
效果如下所示

@NonNull 注解
放置在属性上,将对该属性进行非空检查,如果为空(null),将会抛出NullPointerException;同时为该类添加一个由所有@NonNull属性组成的有参构造器。需要注意的是:由于此时已经有构造器了,Java不再提供无参构造器。示例代码如下:
| 12
 3
 4
 5
 6
 7
 
 | @Dataclass Pc7 {
 @NonNull
 private Integer id;
 private double price;
 private String pcname;
 }
 
 | 
编译后class文件如下,该类中对id属性的操作均进行了非空检查,添加了一个所有@NonNull属性组成(id属性)的有参构造器

@RequiredArgsConstructor 注解
通常我们在实现Spring依赖注入,常常是在需要注入的属性添加@Autowired注解实现(如下所示)。但如果一个类下有很多属性需要注入时,@Autowired就要写一堆了。使用@RequiredArgsConstructor注解可以简化依赖注入
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | ...@Controller
 public class Controller1{
 @Autowired
 private Service1 service1;
 @Autowired
 private Service2 service2;
 ...
 }
 
 | 
这里可以使用这里@RequiredArgsConstructor注解来简化依赖注入操作:首先需要将该注解放在类上,然后在 需要注入的属性前添加final(用法1) 或 在需要注入的属性上添加注解@NonNull(用法2)。用法1 和 用法2 虽然效果一样,但用法1更常用
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | ...@RequiredArgsConstructor(onConstructor = @__(@Autowired))
 @Controller
 public class Controller1{
 
 private final Service1 service1;
 
 @NonNull
 private Service2 service2;
 ...
 }
 
 | 
@Slf4j 注解
为该类添加一个属性名为log的SLF4J日志对象
示例源码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | import lombok.extern.slf4j.Slf4j;
 @Slf4j
 public class User {
 private int id;
 private String name;
 private int age;
 
 public int getAge() {
 log.error("SLF4J getAge: " + age );
 log.debug("SLF4J getAge: " + age );
 return age;
 }
 ...
 }
 
 | 
编译后的class文件如下,该类添加了一个属性名为log的SLF4J日志对象

@SneakyThrows注解
@SneakyThrows注解的作用是其会自动捕获方法中的受检异常,再将其转换为非受检异常并重新对外抛出。从而避免我们显式进行处理(方法签名声明异常抛出 或 捕获处理异常)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 
 | import lombok.SneakyThrows;import java.io.IOException;
 
 public class ExTest {
 public static void main(String[] args) {
 for (int i=1; i<4; i++) {
 System.out.println("i --->>> "+i);
 try{
 int res = foo(i);
 System.out.println("[Foo] --->>> " + res);
 }catch (Exception e) {
 e.printStackTrace();
 }
 }
 }
 
 @SneakyThrows
 private static int foo(int a) {
 if( a==1 ) {
 throw new IOException("IO Error");
 } else if( a==2 ) {
 throw new ClassNotFoundException("System Error");
 }
 
 return a * 100;
 }
 }
 
 | 
结果如下所示,符合预期

Note
- 使用 @Accessors(fluent = true) 注解时,setter/getter的方法名称不会以set、get作为前缀,而是直接使用字段名作为方法名。可是如果某序列化工具是以set、get作为方法名称的前缀来查找相应字段的setter/getter的方法进行序列化、反序列化的话。即会出现序列化、反序列化失败的问题。典型地,存在此问题的序列化工具有:Fastjson 
- 对于 首字母小写、第二个字母大写的字段名,Lombok生成的setter/getter方法与Spring规范不兼容 
例如对于下述类中的yMax属性而言,我们使用IDEA自动生成setter/getter方法,可以看到,其方法名中的y依然是小写
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | public class BorderMsg {private Integer yMax;
 
 
 public Integer getyMax() {
 return yMax;
 }
 
 public void setyMax(Integer yMax) {
 this.yMax = yMax;
 }
 
 }
 
 | 
而当我们使用Lombok生成setter/getter方法时,却发现其方法名中的Y却是大写

对于Spring而言,针对首字母小写、第二个字母大写的字段名,其setter/getter方法名规范使用的是前者。即IDEA自动生成的字段名首字母依然保持小写的方法名,而非Lombok生成的字段名首字母为大写的方法名。故我们在使用Spring中相关工具类时(典型地:RestTemplate类),对于相关字段需通过IDEA来生成setter/getter方法,否则会导致序列化、反序列化失败