Lombok——Java开发常用的代码生成工具。通过使用注解,在编译期将注解替换为相应代码
配置
POM依赖如下
1 2 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方法。示例源码如下:
1 2 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方法。示例源码如下:
1 2 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
1 2 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方法。示例源码如下:
1 2 3 4 5 6 7
| @EqualsAndHashCode class Pc5 implements Serializable { private static final long serialVersionUID = 1; private int id; private double prcie; private String pcname; }
|
编译后class文件如下,该类添加了equals、canEqual、hashCode方法
@ToString 注解
放置在类上,为该类添加toString方法。示例源码如下
1 2 3 4 5 6
| @ToString class Pc6 { private int id; private double price; private String pcname; }
|
编译后class文件如下,该类添加了toString方法
callSuper 属性
对于子类而言,如果我们期望让子类的equals、hashCode、toString方法包含父类的属性,需要将子类上的@EqualsAndHashCode、@ToString注解的callSuper属性设置为True
1 2 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不再提供无参构造器。示例源码如下
1 2 3 4 5 6 7
| @AllArgsConstructor class Pc3 implements Serializable{ private static final long serialVersionUID = 1; private int id; private double prcie; private String pcname; }
|
编译后class文件如下,该类添加了一个全参构造器
@NoArgsConstructor注解为该类添加一个无参构造器。示例源码如下:
1 2 3 4 5 6 7
| @NoArgsConstructor class Pc4 implements Serializable { private static final long serialVersionUID = 1; private int id; private double prcie; private String pcname; }
|
编译后class文件如下,该类添加了一个无参构造器
@Builder、@SuperBuilder 注解
@Builder为类提供了基于建造者模式的创建对象的方法
1 2 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注解。只需在父类、子类同时使用该注解即可
1 2 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不再提供无参构造器。示例代码如下:
1 2 3 4 5 6 7
| @Data class Pc7 { @NonNull private Integer id; private double price; private String pcname; }
|
编译后class文件如下,该类中对id属性的操作均进行了非空检查,添加了一个所有@NonNull属性组成(id属性)的有参构造器
@RequiredArgsConstructor 注解
通常我们在实现Spring依赖注入,常常是在需要注入的属性添加@Autowired注解实现(如下所示)。但如果一个类下有很多属性需要注入时,@Autowired就要写一堆了。使用@RequiredArgsConstructor注解可以简化依赖注入
1 2 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更常用
1 2 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日志对象
示例源码如下:
1 2 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注解的作用是其会自动捕获方法中的受检异常,再将其转换为非受检异常并重新对外抛出。从而避免我们显式进行处理(方法签名声明异常抛出 或 捕获处理异常)
1 2 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依然是小写
1 2 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方法,否则会导致序列化、反序列化失败