1 概述
java代码中,存在很多样板代码,例如类的getter/setter方法、构造器方法、try…catch方法,这些都有特定的规则。于是,偷懒的程序员自然会想到简化这些工作,减少代码的书写量。通过lombok注解,就可以做到这些,下面详细讲述lombok注解的功能。
2 注解原理
lombok的实现原理是在代码编译阶段,识别指定的注解,自动生成字节码文件。
lombok的解析过程如下:

图中,Lombok以一个注解处理器(annotation processor)的角色运行,类似于一个分发器,提供AST给具体的handler。annotation handler需要用户创建,并且通过SPI来注册,实现具体的处理逻辑,修改AST,之后生成的字节码就是修改过的了。
3 注解使用
3.1 使用步骤
要想在idea里面使用lombok,有如下几步。
- 引入pom包
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.8</version> </dependency> -
安装插件

-
项目中使用
@Data public class Product { private int id; private String name; private String desc; }这里使用@Data注解,编译之后的字节码就会含有getter/setter,、无参构造器、equals/hashCode方法和toString()方法。
可见,使用Lombok注解,可以减少很多样板代码,节省我们很大的工作量。(笔者感叹:减少样板代码,一直是程序员的目标,例如mybatis项目的一个目标也是减少样板代码。)
3.2 注解列表
lombok注解列表如下:
| 注解或者简写 | 作用 | 示例 |
|---|---|---|
| val | 可以使用val来声明一个局部变量,该局部变量会被声明为一个常量,类型根据初始化表达式来推断出来 | val personList = new ArrayList(); |
| var | 作用与val类似,不过var用来声明一个局部的可变的变量,类型根据初始化表达式来推断出来 | var personId = 1; |
| @NonNull | 用于方法参数前或者构造器参数前,会自动生成null校验的代码 | public NonNullExample(@NonNull Person person) |
| @Cleanup | 放在局部变量前,会自动关闭该局部变量所引用的资源 | @Cleanup InputStream in = new FileInputStream(args[0]); |
| @Getter/@Setter | 放在类属性前或者类上面,用于生成默认的getter/setter方法 | @Setter(AccessLevel.PROTECTED) private String name; |
| @ToString | 自动生成toString()方法,默认会打印类名和非静态属性 | @ToString public class ToStringExample {} |
| @EqualsAndHashCode | 含有该注解的类会自动生成equals()和hashCode()方法 | @EqualsAndHashCode public class EqualsAndHashCodeExample {} |
| @NoArgsConstructor | 自动生成一个无参的构造器。无参构造器在某些框架下是必须存在的 | |
| @RequiredArgsConstructor | 自动为必需的属性生成一个构造器,必需的属性包括@NonNull注解的属性、final属性 | |
| @AllArgsConstructor | 自动生成一个含有全部属性的构造器。 | |
| @Data | @Data注解相当于同时使用@ToString,@EqualsAndHashCode,@Getter/@Setter,@RequiredArgsConstructor | |
| @Value | @Data注解的不可变版本,使用该注解的类,会自动生成@ToString,@EqualsAndHashCode,@Getter,@RequiredArgsConstructor。该类的实例是不可变的 | |
| @Builder | 使用该注解的类会自动生成建造器模式的指定方法,方便使用建造器模式来创建对象 | @Builder public class BuilderExample {} |
| @SneakyThrows | 使用该注解的方法,会自动生成try…catch语句来捕获受检异常 | @SneakyThrows(UnsupportedEncodingException.class) public Stringutf8ToString(byte[] bytes) {return new String(bytes, "UTF-8");} |
| @Synchronized | 只能放在实例方法或者静态方法上面,用于将方法体内容包围在一个synchronized中 | |
| @With | 当final属性想要改变时,一种方式是新建一个对象。属性上使用@With注解,可以自动生成创建新对象的代码。 | @With private final int age; |
| @Getter(lazy=true) | 自动生成带有lazy逻辑的getter方法,用于final变量。一般当某个变量的计算逻辑很消耗资源时,会lazy计算并且缓存起来,该注解就很有用处。 | @Getter(lazy=true) private final double[] cached = expensive(); |
| @Log | 自动生成一行private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName()); | |
| @Slf4j | 自动生成一行 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class); |
3.3 详细使用
下面,我们将选择几个经常使用的注解,详细讲解其使用。
- @Data注解
@Data注解相当于同时使用@ToString,@EqualsAndHashCode,@Getter/@Setter,@RequiredArgsConstructor。 使用示例如下:@Data public class Product { private int id; private String name; private String desc; }生成的字节码反编译之后如下:
public class Product { private int id; private String name; private String desc; public Product() { } public int getId() { return this.id; } public String getName() { return this.name; } public String getDesc() { return this.desc; } public void setId(final int id) { this.id = id; } public void setName(final String name) { this.name = name; } public void setDesc(final String desc) { this.desc = desc; } public boolean equals(final Object o) { if (o == this) { return true; } else if (!(o instanceof Product)) { return false; } else { Product other = (Product)o; if (!other.canEqual(this)) { return false; } else if (this.getId() != other.getId()) { return false; } else { Object this$name = this.getName(); Object other$name = other.getName(); if (this$name == null) { if (other$name != null) { return false; } } else if (!this$name.equals(other$name)) { return false; } Object this$desc = this.getDesc(); Object other$desc = other.getDesc(); if (this$desc == null) { if (other$desc != null) { return false; } } else if (!this$desc.equals(other$desc)) { return false; } return true; } } } protected boolean canEqual(final Object other) { return other instanceof Product; } public int hashCode() { int PRIME = true; int result = 1; int result = result * 59 + this.getId(); Object $name = this.getName(); result = result * 59 + ($name == null ? 43 : $name.hashCode()); Object $desc = this.getDesc(); result = result * 59 + ($desc == null ? 43 : $desc.hashCode()); return result; } public String toString() { return "Product(id=" + this.getId() + ", name=" + this.getName() + ", desc=" + this.getDesc() + ")"; } }区别主要是多了无参构造器、getter/setter方法、equals/hashCode方法、canEqual()方法和toString()方法。这些方法都可以通过@Data注解让lombok为我们自动生成。
- @Data、@Builder、@NoArgsConstructor和@AllArgsConstructor一起使用
一般而言,使用@Data注解就可以解决大部分问题了。但是,对于有很多属性的类,在创建对象时,使用getter/setter方法很臃肿。因此,lombok提供了@Builder注解,通过建造器模式来创建对象。
使用示例@Data @Builder // 若只是使用@Data和@Builder注解,则构造函数只有含全部属性的构造器,缺少无参构造器。为了同时含有无参构造器和全部属性的构造器,则得同时加上以下两个注解。 @NoArgsConstructor @AllArgsConstructor public class Product { private int id; private String name; private String desc; }自动生成的代码会添加无参构造器、含全部属性的构造器、getter/setter方法、equals/hashCode方法、canEqual()方法和toString()方法,以及一个ProductBuilder内部类用来建造Product。
使用的时候,用户可以方便的创建Product了,示例如下:private static void builderDemo() { Product product = new Product.ProductBuilder().name("phone").desc("a phone").build(); }
4 总结
这里主要是讲了lombok的基本实现原理和基本使用,已经适合于工作中的绝大部分场景了。想要自己实现一个lombok注解,可以参考Implementing a Custom Lombok Annotation
5 参考
Introduction to Project Lombok
Lombok features
Project Lombok: Creating Custom Transformations
Implementing a Custom Lombok Annotation