Lombok插件

2024/1/16 Java常用技术栈

# 1、Lombok介绍

  • Lombok是一个Java库,它通过注解的方式简化了Java代码,提高了开发效率。
  • 使用Lombok,开发人员可以不必手动编写诸如构造器、getter、setter、equals、hashCode和toString等方法,因为Lombok会自动生成这些方法。
  • 此外,Lombok还提供了其他一些有用的注解,如@NonNull、@Getter、@Setter和@ToString等。这些注解可以用于类、字段和参数,以简化代码并减少错误。
  • 使用Lombok可以使代码更加简洁、易于阅读和维护。

简单案例

  • 未使用Lombok时,我们需要手动编写getter和setter方法
public class StudentPO {

    private Long id;

    private String name;

    private Integer age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
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
  • 使用Lombok后,我们只需要添加相应的注解
@Data
public class StudentPO {

    private Long id;

    private String name;

    private Integer age;

}
1
2
3
4
5
6
7
8
9
10

# 2. 配置Lombok

# 2.1、Idea安装Lombok插件

Idea这里我已经安装了Lombok插件,注意看这里其实可以看到Lombok提供了哪些注解可以供我们使用。

Lombok注解需要配合这个Lombok插件使用,不是说我们的类不用getter和setter方法了,而是在编译阶段,Lombok插件会自动帮我们生成这些方法。

# 2.2、引入Lombok依赖

在项目中引入Lombok依赖,所有版本可以在Maven中央仓库中找到。Project Lombok依赖 (opens new window)

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <scope>provided</scope>
</dependency>
1
2
3
4
5
6

# 3、Lombok注解

# 3.1、val、var

val是用于声明局部变量的,它会根据初始化的表达式来对变量类型进行推断。使用val的局部变量会被声明为final。它可以使用与foreach的循环中,val不可以用于成员变量,并且它的初始化表达式是必须的(即必须在声明的时候给出初始化的表达式)。

public static void main(String[] args) {
  val sets = new HashSet<String>();
  val lists = new ArrayList<String>();
  val maps = new HashMap<String, String>();
  //=>相当于如下
  final Set<String> sets2 = new HashSet<>();
  final List<String> lists2 = new ArrayList<>();
  final Map<String, String> maps2 = new HashMap<>();
}
1
2
3
4
5
6
7
8
9

varval相似,只不过var它标记的局部变量并不是final的。

# 3.2、@Data

注解在类上,相当于同时使用了@ToString、@EqualsAndHashCod- e、@Getter、@Setter 和@RequiredArgsConstrutor 这些注解。

@Data
public class StudentPO {

    private Long id;

    private String name;

    private Integer age;

}
1
2
3
4
5
6
7
8
9
10

# 3.3、@EqualsAndHashCode

在类上使用,自动生成 equals 和 hashCode 方法。

/**
 * exclude:排除的字段
 * callSuper:是否调用超类(superclass)的 toString() 方法
 */
@EqualsAndHashCode(exclude = {"id", "shape"}, callSuper = false)
public class LombokDemo {
  private int id;
  private String shap;
}
1
2
3
4
5
6
7
8
9

# 3.4、@Getter/@Setter

  • @Getter:在 JavaBean 或类 JavaBean 中使用,使用此注解会生成对应的 getter 方法。
  • @Setter:在 JavaBean 或类 JavaBean 中使用,使用此注解会生成对应的 setter 方法。
@Setter(AccessLevel.PUBLIC)
@Getter(AccessLevel.PROTECTED)
private int id;
private String shap;
1
2
3
4

# 3.5、@Getter(lazy=true)

当你在一个字段上使用 @Getter(lazy=true) 注解时,Lombok 会生成一个延迟加载的 getter 方法。这意味着该方法不会立即在对象创建时加载字段的值,而是在第一次调用该方法时才加载。这样可以减少内存占用,特别是对于大型对象或从数据库中加载的数据。

public class Example {  
    @Getter(lazy=true)  
    private final String name = loadNameFromDatabase();  
  
    private String loadNameFromDatabase() {  
        // 从数据库中加载名字的逻辑  
        return "John Doe";  
    }  
}
1
2
3
4
5
6
7
8
9

# 3.6、@NoArgsConstructor

在 JavaBean 或类 JavaBean 中使用,使用此注解会生成对应的无参构造方法。

@NoArgsConstructor
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor
public class LombokDemo {
  @NonNull
  private int id;
  @NonNull
  private String shap;
  private int age;
  public static void main(String[] args) {
      new LombokDemo(1, "circle");
      //使用静态工厂方法
      LombokDemo.of(2, "circle");
      //无参构造
      new LombokDemo();
      //包含所有参数
      new LombokDemo(1, "circle", 2);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3.7、AllArgsConstructor

在 JavaBean 或类 JavaBean 中使用,使用此注解会生成对应的全参构造方法。

@AllArgsConstructor
public class LombokDemo {
  @NonNull
  private int id;
  @NonNull
  private String shap;
  private int age;
  public static void main(String[] args) {
      //包含所有参数
      new LombokDemo(1, "circle", 2);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# RequiredArgsConstructor

在 JavaBean 或类 JavaBean 中使用,把所有@NonNull属性和final属性作为参数的构造函数,如果指定 staticName = “of”参数,同时还会生成一个返回类对象的静态工厂方法。

// staticName = "of"表示生成的构造函数名称将为of`。
@RequiredArgsConstructor(staticName = "of")
public class Person {  
    private final String name;  
    private String address;

    // 相当于
    public static void of(String name) {
        this.name = name;
    }
}
1
2
3
4
5
6
7
8
9
10
11

Person 类有两个字段:name 和 address。由于 name 字段是 final 的,Lombok 将自动生成一个包含 name 参数的构造函数。而 address 字段是非 final 的,因此不会自动添加到构造函数中。

# 3.9、@Value

用在类上,是@Data 的不可变形式,相当于为属性添加 final 声明,只提供 getter 方法,而不提供 setter 方法。

@Value
public class LombokDemo {
  @NonNull
  private int id;
  @NonNull
  private String shap;
  private int age;
  //相当于
  private final int id;
  public int getId() {
      return this.id;
  }
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 3.10、@NonNull

用在方法参数前,会自动对该参数进行非空校验,为空抛出 NullPointerException。

public void notNullExample(@NonNull String string) {
    string.length();
}
//=>相当于
public void notNullExample(String string) {
  if (string != null) {
      string.length();
  } else {
      throw new NullPointerException("null");
  }
}
1
2
3
4
5
6
7
8
9
10
11

# 3.11、@Builder

用在类、构造器、方法上,为你提供复杂的 builder APIs。 让你可以像如下方式一样调用 Person.builder().name("Adam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build()

@Builder
public class BuilderExample {
  private String name;
  private int age;
  @Singular
  private Set<String> occupations;
  public static void main(String[] args) {
      BuilderExample test = BuilderExample.builder().age(11).name("test").build();
  }
}
1
2
3
4
5
6
7
8
9
10

# 3.12、@Singular

用于集合字段。当你在一个集合字段上使用 @Singular 注解时,Lombok 会生成一个添加元素的方法,该方法接受一个参数,并将该参数添加到集合中。

public class Person {  
    @Singular 
    private List<String> hobbies = new ArrayList<>();  
}
1
2
3
4

hobbies 是一个集合字段,使用了 @Singular 注解。这意味着 Lombok 将自动生成一个名为 addHobby 的方法,该方法接受一个字符串参数,并将该参数添加到 hobbies 集合中。

# 3.13、@SneakyThrows

自动抛受检异常,而无需显式在方法上使用 throws 语句。

public class Test {
  @SneakyThrows()
  public void read() {
      InputStream inputStream = new FileInputStream("");
  }
  @SneakyThrows
  public void write() {
      throw new UnsupportedEncodingException();
  }
  //相当于
  public void read() throws FileNotFoundException {
      InputStream inputStream = new FileInputStream("");
  }
  public void write() throws UnsupportedEncodingException {
      throw new UnsupportedEncodingException();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 3.14、@Synchronized

用在方法上,将方法声明为同步的,并自动加锁。

public class SynchronizedDemo {
  @Synchronized
  public static void hello() {
      System.out.println("world");
  }
  //相当于
  private static final Object $LOCK = new Object[0];
  public static void hello() {
      synchronized ($LOCK) {
          System.out.println("world");
      }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 3.15、@Log

根据不同的注解生成不同类型的 log 对象,但是实例名称都是 log,有六种可选实现类

  • @CommonsLog:Creates log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
  • @Log:Creates log = java.util.logging.Logger.getLogger(LogExample.class.getName());
  • @Log4j:Creates log = org.apache.log4j.Logger.getLogger(LogExample.class);
  • @Log4j2:Creates log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
  • @Slf4j:Creates log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
  • @XSlf4j:Creates log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
import lombok.Log;  
  
@Log  
public class MyClass {  
    public void doSomething() {  
        // 执行一些操作  
    }  
}

// @Log 注解告诉 Lombok 在 MyClass 类上自动生成日志记录代码。
// 具体的日志记录代码取决于你使用的日志框架。如果你使用的是 SLF4J 日志框架,Lombok 将自动生成类似于以下的代码:

import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
  
public class MyClass {  
    private static final Logger log = LoggerFactory.getLogger(MyClass.class);  
  
    public void doSomething() {  
        log.info("Doing something");  
        // 执行一些操作  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 3.16、@Cleanup

自动管理资源,用在局部变量之前,在当前变量范围内即将执行完毕退出前会清理资源,生成 try-finally 的代码关闭流。

public static void main(String[] args) {
  try {
      @Cleanup 
      InputStream inputStream = new FileInputStream(args[0]);
  } catch (FileNotFoundException e) {
      e.printStackTrace();
  }
  //=>相当于
  InputStream inputStream = null;
  try {
      inputStream = new FileInputStream(args[0]);
  } catch (FileNotFoundException e) {
      e.printStackTrace();
  } finally {
      if (inputStream != null) {
          try {
              inputStream.close();
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 3.17、@ToString

在 JavaBean 或类 JavaBean 中使用,使用此注解会自动复写 toString 方法。

/**
 * exclude:排除的字段
 * callSuper:是否调用超类(superclass)的 toString() 方法
 * includeFieldNames:是否包含字段名称
 */
@ToString(exclude = "id", callSuper = true, includeFieldNames = true)
public class LombokDemo {
  private int id;
  private String name;
  private int age;
  public static void main(String[] args) {
      //输出LombokDemo(super=LombokDemo@48524010, name=null, age=0)
      System.out.println(new LombokDemo());
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 3.18、@With

作用于类,生成多个 with + 变量名的方法(个数为所有成员变量,不包含 @NonNull),作用于变量,生成 with + 变量名的方法 返回当前对象,需要提供全参(不包含静态变量)构造方法

@AllArgsConstructor
@With
public class Test {
	private final String name;
	
	private Integer age;
}
// 相当于
public class Test {
	private final String name;
  	
  	private Integer age;
	
  	public Test(String name, Integer age) {
    	this.name = name;
    	this.age = age;
    }
	
  	public Test withName(String name) {
    	return new Test(name, this.age);
   	}
	
  	public Test withAge(Integer age) {
  		return new Test(this.name, age);
  	}
}
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

# 4、Experimental注解

lombok.experimental 包下,lombok除了已经推荐使用的基本功能,还维护了一个创新型的注解,有些功能有违常规对java认知,或者只支持eclipse,其他IDE支持有问题,甚至某些环境完全不可用。因此没有正式使用。但是的确很有创意,这些注解已经在jar中提供,只不过它是归在lombok.experimental.包中;而基本功能在lombok.包中。

# 4.1、@Accessors

默认情况下,没什么作用,需要设置参数

  • chain:为true时,setter链式返回,即setter的返回值为this
  • fluent:为true时,默认设置chain为true,setter的方法名修改为字段名
  • prefix:set方法忽略指定的前缀。
@Data
@Accessors(chain=true)
public class User {
    private Integer id;
    private String name;
    private Integer age;

    public static void main(String[] args) {
        //开起chain=true后可以使用链式的set
        User user=new User().setAge(31).setName("pollyduan");//返回对象
        System.out.println(user);
    }

}

@Data
@Accessors(fluent=true)
public class User {
    private Integer id;
    private String name;
    private Integer age;

    public static void main(String[] args) {
        //fluent=true开启后默认chain=true,故这里也可以使用链式set
        User user=new User().age(31).name("pollyduan");//不需要写set
        System.out.println(user);
    }

}


@Data
@Accessors(prefix = "f")
public class User {
    private String fName = "Hello, World!";

    public static void main(String[] args) {
        User user=new User();
        user.setName("pollyduan");//注意方法名
        System.out.println(user);
    }

}
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

# 4.2、@Delegate

代理模式,把字段的方法代理给类,默认代理所有方法

  • types:指定代理的方法
  • excludes:和types相反
public class Example {

    private interface Add {
        boolean add(String x);
        boolean addAll(Collection<? extends String> x);
    }

    private @Delegate(types = Add.class) List<String> strings;
}

// 生成结果
public class Example {
    private List<String> strings;

    public Example() {
    }

    public boolean add(String x) {
        return this.strings.add(x);
    }

    public boolean addAll(Collection<? extends String> x) {
        return this.strings.addAll(x);
    }

    private interface Add {
        boolean add(String var1);

        boolean addAll(Collection<? extends String> var1);
    }
}
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

# 4.3、@ExtensionMethod

拓展方法,向现有类型“添加”方法,而无需创建新的派生类型。有点像kotlin的扩展函数。

@ExtensionMethod({Arrays.class, Extensions.class})
public class Example {

    public static void main(String[] args) {
        int[] intArray = {5, 3, 8, 2};
        intArray.sort();
        int num = 1;
        num = num.increase();

        Arrays.stream(intArray).forEach(System.out::println);
        System.out.println("num = " + num);
    }
}

class Extensions {
    public static int increase(int num) {
        return ++num;
    }
}

// 生成如下
public class Example {
    public Example() {
    }

    public static void main(String[] args) {
        int[] intArray = new int[]{5, 3, 8, 2};
        Arrays.sort(intArray);
        int num = 1;
        int num = Extensions.increase(num);
        IntStream var10000 = Arrays.stream(intArray);
        PrintStream var10001 = System.out;
        System.out.getClass();
        var10000.forEach(var10001::println);
        System.out.println("num = " + num);
    }
}

/**
 * 输出结果:
 * 2
 * 3
 * 5
 * 8
 * num = 2
 */
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

# 4.4、@FieldDefaults

定义类、字段的修饰符

  • AccessLevel:访问权限修饰符
  • makeFinal:是否加final
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class Gulnazar {
	String name = "xygalaxy";
}

// ==>相当于
public class Gulnazar {
  	private final String name = "xygalaxy";
}
1
2
3
4
5
6
7
8
9

# 4.5、@FieldNameConstants

为你的字段生成一个以字段名称为值的常量

  • prefix:前缀
  • suffix:后缀
@FieldNameConstants
public class FieldNameConstantsExample {
  private final String iAmAField;
  private final int andSoAmI;
  @FieldNameConstants.Exclude 
  private final int asAmI;
}

//==>相当于
public class FieldNameConstantsExample {
  private final String iAmAField;
  private final int andSoAmI;
  private final int asAmI;
  
  public static final class Fields {
    public static final String iAmAField = "iAmAField";
    public static final String andSoAmI = "andSoAmI";
  }
}

// 最终其实也就是相当于
public class FieldNameConstantsExample {
  private static final String iAmAField = "iAmAField";
  private static final int andSoAmI = "andSoAmI";
  private final int asAmI;
}
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
public class Example {
    @FieldNameConstants(prefix = "PREFIX_", suffix = "_SUFFIX")
    private String foo;
}

public class Example {
    public static final String PREFIX_FOO_SUFFIX = "foo";
    private String foo;

    public Example() {
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 4.6、@Helper

让你可以在方法内部中写方法

public class HelperExample {
  int someMethod(int arg1) {
    int localVar = 5;

    @Helper class Helpers {
      int helperMethod(int arg) {
        return arg + localVar;
      }
    }

    return helperMethod(10);
  }
}

// 相当于
public class HelperExample {
  int someMethod(int arg1) {
    int localVar = 5;

    class Helpers {
      int helperMethod(int arg) {
        return arg + localVar;
      }
    }
    Helpers $Helpers = new Helpers();

    return $Helpers.helperMethod(10);
  }
}
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

# 4.7、@NonFinal

设置不为Final,@FieldDefaults和@Value也有这功能

public class ZhuXuDan {
	@NonFinal String age;
	
	@NonFinal final String  name = "xygalaxy";	// 必须初始化
}
1
2
3
4
5

# 4.8、@PackagePrivate

作用于类和变量,相当于访问修饰符的 default,没什么用

# 4.9、@SuperBuilder

支持对于基类成员变量赋值,算是 @Builder 的升级版

# 4.10、@Tolerate

实现对冲突的兼容,作用于方法上,没什么大用,可以配合 @Builder 使用

# 4.11、@UtilityClass

作用于类,将类标记为 final,并且类、内部类中的方法、字段都标记为 static

# 5、Lombok原理解析

# 5.1、JSR-269提案

在JDK 5之后,Java语言提供了对注解(Annotations)的支持。注解原本是设计为在程序运行期间发挥作用的。然而,在JDK 6中,通过JSR-269提案,插入式注解处理器(Pluggable Annotation Processors)的标准API被引入,使得注解处理器可以在编译期对代码中的特定注解进行处理,影响前端编译器的工作过程。

# 5.2、Lombok编译过程

  • 当编译器在编译Java代码时,Lombok的注解处理器会解析Lombok的注解,并在编译期间自动生成对应的方法代码。
  • 例如,当使用@Data注解时,相当于同时使用了@Getter、@Setter、@EqualsAndHashCode、@ToString等注解。编译器会在编译期根据类的成员变量自动生成对应的getter/setter、equals/hashCode、toString等方法。

Lombok编译过程

  • javac对源代码进行分析,生成了一棵抽象语法树(AST)
  • 运行过程中调用实现了“JSR 269 API”的Lombok程序
  • 此时Lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点
  • javac使用修改后的抽象语法树(AST)生成字节码文件,即给class增加新的节点(代码块)

参考:最全一篇Lombok使用讲解 (opens new window)