Lombok 快速入门 Lombok 简介 Lombok 是一种 Java 实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注释实现这一目的。通过在开发环境中实现 Lombok,开发人员可以节省构建诸如 hashCode()
和 equals()
、getter / setter
这样的方法以及以往用来分类各种 accessor 和 mutator 的大量时间。
Lombok 安装 由于 Lombok 仅在编译阶段生成代码,所以使用 Lombok 注解的源代码,在 IDE 中会被高亮显示错误,针对这个问题可以通过安装 IDE 对应的插件来解决。具体的安装方式可以参考:Setting up Lombok with Eclipse and Intellij
使 IntelliJ IDEA 支持 Lombok 方式如下:
Intellij 设置支持注解处理
点击 File > Settings > Build > Annotation Processors
勾选 Enable annotation processing
安装插件
点击 Settings > Plugins > Browse repositories
查找 Lombok Plugin 并进行安装
重启 IntelliJ IDEA
将 lombok 添加到 pom 文件
1 2 3 4 5 <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.16.8</version > </dependency >
Lombok 使用 Lombok 提供注解 API 来修饰指定的类:
@Getter and @Setter @Getter and @Setter Lombok 代码:
1 2 @Getter @Setter private boolean employed = true ;@Setter(AccessLevel.PROTECTED) private String name;
等价于 Java 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private boolean employed = true ;private String name;public boolean isEmployed () { return employed; } public void setEmployed (final boolean employed) { this .employed = employed; } protected void setName (final String name) { this .name = name; }
@NonNull @NonNull Lombok 代码:
1 2 @Getter @Setter @NonNull private List<Person> members;
等价于 Java 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @NonNull private List<Person> members;public Family (@NonNull final List<Person> members) { if (members == null ) throw new java .lang.NullPointerException("members" ); this .members = members; } @NonNull public List<Person> getMembers () { return members; } public void setMembers (@NonNull final List<Person> members) { if (members == null ) throw new java .lang.NullPointerException("members" ); this .members = members; }
@ToString @ToString Lombok 代码:
1 2 3 4 5 6 @ToString(callSuper=true,exclude="someExcludedField") public class Foo extends Bar { private boolean someBoolean = true ; private String someStringField; private float someExcludedField; }
等价于 Java 源码:
1 2 3 4 5 6 7 8 9 10 11 12 public class Foo extends Bar { private boolean someBoolean = true ; private String someStringField; private float someExcludedField; @java .lang.Override public java.lang.String toString () { return "Foo(super=" + super .toString() + ", someBoolean=" + someBoolean + ", someStringField=" + someStringField + ")" ; } }
@EqualsAndHashCode @EqualsAndHashCode Lombok 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 @EqualsAndHashCode(callSuper=true,exclude={"address","city","state","zip"}) public class Person extends SentientBeing { enum Gender { Male, Female } @NonNull private String name; @NonNull private Gender gender; private String ssn; private String address; private String city; private String state; private String zip; }
等价于 Java 源码:
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 public class Person extends SentientBeing { enum Gender { Male , Female ; } @NonNull private String name; @NonNull private Gender gender; private String ssn; private String address; private String city; private String state; private String zip; @java .lang.Override public boolean equals (final java.lang.Object o) { if (o == this ) return true ; if (o == null ) return false ; if (o.getClass() != this .getClass()) return false ; if (!super .equals(o)) return false ; final Person other = (Person)o; if (this .name == null ? other.name != null : !this .name.equals(other.name)) return false ; if (this .gender == null ? other.gender != null : !this .gender.equals(other.gender)) return false ; if (this .ssn == null ? other.ssn != null : !this .ssn.equals(other.ssn)) return false ; return true ; } @java .lang.Override public int hashCode () { final int PRIME = 31 ; int result = 1 ; result = result * PRIME + super .hashCode(); result = result * PRIME + (this .name == null ? 0 : this .name.hashCode()); result = result * PRIME + (this .gender == null ? 0 : this .gender.hashCode()); result = result * PRIME + (this .ssn == null ? 0 : this .ssn.hashCode()); return result; } }
@Data @Data Lombok 代码:
1 2 3 4 5 6 @Data(staticConstructor="of") public class Company { private final Person founder; private String name; private List<Person> employees; }
等价于 Java 源码:
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 59 60 public class Company { private final Person founder; private String name; private List<Person> employees; private Company (final Person founder) { this .founder = founder; } public static Company of (final Person founder) { return new Company (founder); } public Person getFounder () { return founder; } public String getName () { return name; } public void setName (final String name) { this .name = name; } public List<Person> getEmployees () { return employees; } public void setEmployees (final List<Person> employees) { this .employees = employees; } @java .lang.Override public boolean equals (final java.lang.Object o) { if (o == this ) return true ; if (o == null ) return false ; if (o.getClass() != this .getClass()) return false ; final Company other = (Company)o; if (this .founder == null ? other.founder != null : !this .founder.equals(other.founder)) return false ; if (this .name == null ? other.name != null : !this .name.equals(other.name)) return false ; if (this .employees == null ? other.employees != null : !this .employees.equals(other.employees)) return false ; return true ; } @java .lang.Override public int hashCode () { final int PRIME = 31 ; int result = 1 ; result = result * PRIME + (this .founder == null ? 0 : this .founder.hashCode()); result = result * PRIME + (this .name == null ? 0 : this .name.hashCode()); result = result * PRIME + (this .employees == null ? 0 : this .employees.hashCode()); return result; } @java .lang.Override public java.lang.String toString () { return "Company(founder=" + founder + ", name=" + name + ", employees=" + employees + ")" ; } }
@Cleanup @Cleanup Lombok 代码:
1 2 3 4 5 6 7 8 9 public void testCleanUp () { try { @Cleanup ByteArrayOutputStream baos = new ByteArrayOutputStream (); baos.write(new byte [] {'Y' ,'e' ,'s' }); System.out.println(baos.toString()); } catch (IOException e) { e.printStackTrace(); } }
等价于 Java 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 public void testCleanUp () { try { ByteArrayOutputStream baos = new ByteArrayOutputStream (); try { baos.write(new byte []{'Y' , 'e' , 's' }); System.out.println(baos.toString()); } finally { baos.close(); } } catch (IOException e) { e.printStackTrace(); } }
@Synchronized @Synchronized Lombok 代码:
1 2 3 4 5 6 private DateFormat format = new SimpleDateFormat ("MM-dd-YYYY" );@Synchronized public String synchronizedFormat (Date date) { return format.format(date); }
等价于 Java 源码:
1 2 3 4 5 6 7 8 private final java.lang.Object $lock = new java .lang.Object[0 ];private DateFormat format = new SimpleDateFormat ("MM-dd-YYYY" );public String synchronizedFormat (Date date) { synchronized ($lock) { return format.format(date); } }
@SneakyThrows @SneakyThrows Lombok 代码:
1 2 3 4 @SneakyThrows public void testSneakyThrows () { throw new IllegalAccessException (); }
等价于 Java 源码:
1 2 3 4 5 6 7 public void testSneakyThrows () { try { throw new IllegalAccessException (); } catch (java.lang.Throwable $ex) { throw lombok.Lombok.sneakyThrow($ex); } }
示例源码
示例源码:javalib-bean
Lombok 使用注意点 谨慎使用 @Builder
在类上标注了 @Data
和 @Builder
注解的时候,编译时,lombok 优化后的 Class 中会没有默认的构造方法。在反序列化的时候,没有默认构造方法就可能会报错。
【示例】使用 @Builder
不当导致 json 反序列化失败
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Data @Builder public class BuilderDemo01 { private String name; public static void main (String[] args) throws JsonProcessingException { BuilderDemo01 demo01 = BuilderDemo01.builder().name("demo01" ).build(); ObjectMapper mapper = new ObjectMapper (); String json = mapper.writeValueAsString(demo01); BuilderDemo01 expectDemo01 = mapper.readValue(json, BuilderDemo01.class); System.out.println(expectDemo01.toString()); } }
运行时会抛出异常:
1 2 3 4 5 6 7 8 9 10 11 12 Exception in thread "main" com.fasterxml .jackson .databind .exc .MismatchedInputException : Cannot construct instance of `io.github .dunwu .javatech .bean .lombok .BuilderDemo01` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (String)"{" name":" demo01"}" ; line: 1, column: 2] at com.fasterxml .jackson .databind .exc .MismatchedInputException .from (MismatchedInputException.java :63 ) at com.fasterxml .jackson .databind .DeserializationContext .reportInputMismatch (DeserializationContext.java :1432 ) at com.fasterxml .jackson .databind .DeserializationContext .handleMissingInstantiator (DeserializationContext.java :1062 ) at com.fasterxml .jackson .databind .deser .BeanDeserializerBase .deserializeFromObjectUsingNonDefault (BeanDeserializerBase.java :1297 ) at com.fasterxml .jackson .databind .deser .BeanDeserializer .deserializeFromObject (BeanDeserializer.java :326 ) at com.fasterxml .jackson .databind .deser .BeanDeserializer .deserialize (BeanDeserializer.java :159 ) at com.fasterxml .jackson .databind .ObjectMapper ._readMapAndClose (ObjectMapper.java :4218 ) at com.fasterxml .jackson .databind .ObjectMapper .readValue (ObjectMapper.java :3214 ) at com.fasterxml .jackson .databind .ObjectMapper .readValue (ObjectMapper.java :3182 ) at io.github .dunwu .javatech .bean .lombok .BuilderDemo01 .main (BuilderDemo01.java :22 )
【示例】使用 @Builder
正确方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Data @Builder @NoArgsConstructor @AllArgsConstructor public class BuilderDemo02 { private String name; public static void main (String[] args) throws JsonProcessingException { BuilderDemo02 demo02 = BuilderDemo02.builder().name("demo01" ).build(); ObjectMapper mapper = new ObjectMapper (); String json = mapper.writeValueAsString(demo02); BuilderDemo02 expectDemo02 = mapper.readValue(json, BuilderDemo02.class); System.out.println(expectDemo02.toString()); } }
@Data
注解和继承使用 @Data
注解时,则有了 @EqualsAndHashCode
注解,那么就会在此类中存在 equals(Object other)
和 hashCode()
方法,且不会使用父类的属性,这就导致了可能的问题。比如,有多个类有相同的部分属性,把它们定义到父类中,恰好 id(数据库主键)也在父类中,那么就会存在部分对象在比较时,它们并不相等,这是因为:lombok 自动生成的 equals(Object other)
和 hashCode()
方法判定为相等,从而导致和预期不符。
修复此问题的方法很简单:
使用 @Data
时,加上 @EqualsAndHashCode(callSuper=true)
注解。
使用 @Getter @Setter @ToString
代替 @Data
并且自定义 equals(Object other)
和 hashCode()
方法。
【示例】测试 @Data
和 @EqualsAndHashCode
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 @Data @ToString(exclude = "age") @EqualsAndHashCode(exclude = { "age", "sex" }) public class Person { protected String name; protected Integer age; protected String sex; } @Data @EqualsAndHashCode(callSuper = true, exclude = { "address", "city", "state", "zip" }) public class EqualsAndHashCodeDemo extends Person { @NonNull private String name; @NonNull private Gender gender; private String ssn; private String address; private String city; private String state; private String zip; public EqualsAndHashCodeDemo (@NonNull String name, @NonNull Gender gender) { this .name = name; this .gender = gender; } public EqualsAndHashCodeDemo (@NonNull String name, @NonNull Gender gender, String ssn, String address, String city, String state, String zip) { this .name = name; this .gender = gender; this .ssn = ssn; this .address = address; this .city = city; this .state = state; this .zip = zip; } public enum Gender { Male, Female } } @Test @DisplayName("测试 @EqualsAndHashCode") public void testEqualsAndHashCodeDemo () { EqualsAndHashCodeDemo demo1 = new EqualsAndHashCodeDemo ("name1" , EqualsAndHashCodeDemo.Gender.Female, "ssn" , "xxx" , "xxx" , "xxx" , "xxx" ); EqualsAndHashCodeDemo demo2 = new EqualsAndHashCodeDemo ("name1" , EqualsAndHashCodeDemo.Gender.Female, "ssn" , "ooo" , "ooo" , "ooo" , "ooo" ); Assertions.assertEquals(demo1, demo2); Person person = new Person (); person.setName("张三" ); person.setAge(20 ); person.setSex("男" ); Person person2 = new Person (); person2.setName("张三" ); person2.setAge(18 ); person2.setSex("男" ); Person person3 = new Person (); person3.setName("李四" ); person3.setAge(20 ); person3.setSex("男" ); Assertions.assertEquals(person2, person); Assertions.assertNotEquals(person3, person); }
上面的单元测试可以通过,但如果将 @EqualsAndHashCode(callSuper = true, exclude = { "address", "city", "state", "zip" })
注掉就会报错。
参考资料