一、概述

1、简介

Lombok是一个Java库,它可以自动插入到编辑器和构建工具中,通过注解自动生成setter/getter方法或构造函数、自动化日志变量等,简化Java代码编写。

一个简单的例子如下,User类的name属性使用了lombok的@Getter@Setter注解:

import lombok.Getter;
import lombok.Setter;

public class User {
	@Getter
	@Setter 
	private String name;
	private int age;
}

运行javac -cp lombok.jar User.java编译User类,编译后的class中可以看到会自动生成name属性的setter/getter方法:

public class User
{
	private String name;
	private int age;

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

2、下载

下载lombok.jar,此处使用的是1.18.30版本。

3、安装

此处使用Eclipse。

双击运行下载的lombok.jar或使用命令java -jar lombok.jar运行,运行后程序会扫描安装的Eclipse,也可以点击Specify location...按钮选择Eclipse的安装目录:

选择要安装的Eclipse后,点击Install / Update按钮即可成功安装。安装后还需要:

  • lombok.jar添加到工程中

  • 退出并重启Eclipse

  • 重新构建工程

二、注解

1、var

使用var可以定义局部变量。

User类:

@ToString
public class User {
	@Setter
	private String name; 
	@Setter
	private int age;
}

测试代码:

public static void main(String[] args) {
	var name = "albert";
	var age = 30;
	var user = new User();
	user.setAge(age);
	user.setName(name);
	System.out.println(user);
}

编译后:

public static void main(String[] args)
{
    String name = "albert";
    int age = 30;
    User user = new User();
    user.setAge(age);
    user.setName(name);
    System.out.println(user);
}

运行结果:

User(name=albert, age=30)

2、NonNull

可以在字段上或方法的参数上使用@NonNull,此注解会在方法顶部插入空判断逻辑:

if (param == null) throw new NullPointerException("param is marked non-null but is null");
  • 方法参数使用注解:
public void printAddress(@NonNull Address address){
	System.out.println("The address is " + address);
}

编译后:

public void printAddress(@NonNull Address address)
{
	if (address == null) throw new NullPointerException("address is marked non-null but is null");
	System.out.println("The address is " + address);
}
  • 字段上使用:
public class User {
	@NonNull
	@Setter
	private Address address;
}

编译后:

public class User
{
	@NonNull
	private Address address;

	public void setAddress(@NonNull Address address)
	{
		if (address == null) throw new NullPointerException("address is marked non-null but is null"); 
		this.address = address;
	}
}

3、Cleanup

使用@Cleanup可以自动管理资源,安全地调用close()方法。

try {
	@Cleanup 
	InputStream in = new FileInputStream("some/file");
} catch (Exception e) {
	e.printStackTrace();
}

编译后:

try
{
	InputStream in = new FileInputStream("some/file");

	if (Collections.singletonList(in).get(0) != null) in.close(); 
}
catch (Exception e)
{
	e.printStackTrace();
}

如果清理的对象没有close()方法,而是需要调用其他的无参方法,则可以通过配置@Cleanup("xxx")指定。

User类:

public class User {
	
	public void dispose(){
		
	}
}

测试代码:

@Cleanup("dispose")
User user = new User();

编译后:

User user = new User();

if (Collections.singletonList(user).get(0) != null) user.dispose();

4、Getter/Setter

可以在任何字段上使用@Getter@Setter注解,lombok会自动生成默认的getter/setter方法。生成的getter/setter方法默认是public的,可以设置AccessLevel属性来调整访问级别,此属性可选的值有PUBLIC、PROTECTED、PACKAGE、PRIVATE、NONE,其中NONE表示禁止生成,这样可样覆盖类上的@Getter@Setter@Data注释的行为。

可以在类上使用@Getter@Setter注解,该类中的所有非静态字段会自动生成getter/setter方法。

@Setter
@Getter
public class User {
	
	private String name;
	private int age;
	@Getter(AccessLevel.PROTECTED)
	@Setter(AccessLevel.NONE)
	private Address address;
}

编译后:

public class User {
	private String name;
	private int age;
	private Address address;

	protected Address getAddress() {
		return this.address;
	}

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

	public void setAge(int age) {
		this.age = age;
	}

	public String getName() {
		return this.name;
	}

	public int getAge() {
		return this.age;
	}
}
  • lazy

如果使用@Getter(lazy=true),则在lombok生成的getter方法中会缓存第一次调用getter方法时获取的值。

public class Knowledge {

	@Getter(lazy=true)
	private final String[] values = expensive();
	
	private String[] expensive(){
		String[] ret = null;
		//一些耗时的操作...
		return ret;
	}
}

编译后:

public class Knowledge
{
	private final AtomicReference<Object> values = new AtomicReference();

	private String[] expensive()
	{
		String[] ret = null;
		return ret;
	}

	public String[] getValues()
	{
		Object value = this.values.get(); 
		if (value == null) {
			synchronized (this.values) { 
				value = this.values.get(); 
				if (value == null) { 
					String[] actualValue = expensive(); 
					value = actualValue == null ? this.values : actualValue; 
					this.values.set(value); 
				} 
			} 
		}
		return (String[])(value == this.values ? null : value);
	}
}

5、ToString

使用@ToString注解可以自动生成toString()方法的实现;默认情况下toString()将打印所有非静态字段,可以使用@ToString.Exclude标记不需要输出的字段。或者可以设置@ToString(onlyExplicitlyIncluded = true),并用@ToString.Include准确指定要输出的字段。

可以通过设置includeFieldNames为true或false来控制输出时是否打印字段名称,默认值为true。

如果设置callSuper为true,则会将父类的输出也包含到输出中。除此之外,还可以通过@ToString.Include(rank = -1)来设置输出的顺序,rank默认为0,较大的值先输出。

User类:

@ToString
@Getter
@Setter
public class User {

	private String name;
	@ToString.Exclude
	private int age;
	private Address address;
}

Address类:

@ToString(includeFieldNames=false)
public class Address {

	@Setter
	private String country;
}

测试代码:

public static void main(String[] args) {
	User user = new User();
	user.setName("albert");
	user.setAge(30);
	Address address = new Address();
	address.setCountry("China");
	user.setAddress(address);
	System.out.println(user);
}

输出:

User(name=albert, address=Address(China))

如果修改Address的includeFieldNames为true,则输出:

User(name=albert, address=Address(country=China))

6、EqualsAndHashCode

在类上添加@EqualsAndHashCode注解可以让lombok生成equals(Object other)hashCode()方法的实现,默认情况下会使用所有非静态、非瞬态(non-transient)字段,可以使用@EqualsAndHashCode.Include@EqualsAndHashCode.Exclude来指定使用的字段,它也可以配置onlyExplicitlyIncluded属性。

Book类:

@EqualsAndHashCode
@Setter
public class Book {

	private String isbn;
	@EqualsAndHashCode.Exclude
	private String name;
}

测试代码:

public static void main(String[] args) {
	Book book = new Book();
	book.setIsbn("ISBN7-1000");
	book.setName("lombok");
	
	Book another = new Book();
	another.setIsbn("ISBN7-1000");
	another.setName("project lombok");
	
	System.out.println(book.equals(another));
}

输出:true

7、XxxArgsConstructor

@NoArgsConstructor@RequiredArgsConstructor@AllArgsConstructor三种注解都会生成构造函数。

  • NoArgsConstructor

@NoArgsConstructor注解会生成一个无参数构造函数,如果类中的final类型字段而无法生成,则会导致编译错误,除非使用@NoArgsConstructor(force = true)将所有final字段初始化。

样例:

@NoArgsConstructor(force=true)
public class User {

	private final String name;
	private int age;
	
	public User(String name, int age){
		this.name = name;
		this.age = age;
	}
}

编译后:

public class User
{
	private final String name;
	private int age;

	public User(String name, int age)
	{
		this.name = name;
		this.age = age;
	}

	public User()
	{
		this.name = null;
	}
}
  • RequiredArgsConstructor

@RequiredArgsConstructor注解为需要特殊处理的字段例如未初始化的final字段生成对应参数的构造函数。

样例:

@RequiredArgsConstructor
public class User {

	private final String name;
	private int age;
}

编译后:

public class User
{
	private final String name;
	private int age;

	public User(String name)
	{
		this.name = name;
	}
}
  • AllArgsConstructor

@AllArgsConstructor注解为类中的每个字段生成一个对应参数的构造函数。

样例:

@AllArgsConstructor
public class User {

	private String name;
	private int age;
}

编译后:

public class User
{
	private String name;
	private int age;

	public User(String name, int age)
	{
		this.name = name; this.age = age;
	}
}
这三个注释都可以通过staticName="xxx"来生成私有的构造函数,并且会生成一个通过私有构造函数创建对象的静态工厂方法。

样例:

@AllArgsConstructor(staticName="getInstance")
public class User {

	private final String name;
	private int age;
	private Address address;
}

编译后:

public class User
{
	private final String name;
	private int age;
	private Address address;

	private User(String name, int age, Address address)
	{
		this.name = name; 
		this.age = age; 
		this.address = address; 
	} 
	public static User getInstance(String name, int age, Address address) { 
		return new User(name, age, address); 
	}
}

8、Data

@Data注解是将@ToString@EqualsAndHashCode@Getter@Setter@RequiredArgsConstructor注解整合在一起的快捷注解,它可以自动生成所有字段的getter方法,所有非final字段的setter方法,以及toStringequalshashCode方法的实现。

样例:

@Data(staticConstructor = "getInstance")
public class User {
	private final String name;
	@Getter
	@Setter(AccessLevel.NONE)
	private int age;
}

说明:

  • @Data的staticConstructor参数会生成私有构造函数和返回新实例的静态方法
//name属性是final类型的,会生成相应的构造函数(@RequiredArgsConstructor)
private User(String name) { 
	this.name = name; 
} 

public static User getInstance(String name) { 
	return new User(name);
}
  • @Setter@Getter@RequiredArgsConstructor等注解可以通过配置AccessLevel来设置方法的可见性

9、Value

@Value@Data的不可变形式,默认情况下,所有字段都会生成final类型,并且不会生成setter方法,类本身也默认是 final类型的。

样例:

@Value
public class User {
	private String name;
	private int age;
}

编译后:


public final class User
{
	private final String name;
	private final int age;
	public User(String name, int age) { 
		this.name = name; 
		this.age = age;
	}

	public String getName()
	{
		return this.name; 
	} 
	public int getAge() { 
		return this.age; 
	} 
	public boolean equals(Object o) { 
		if (o == this) return true; 
		if (!(o instanceof User)) return false; 
		User other = (User)o; 
		if (getAge() != other.getAge()) return false; 
		Object this$name = getName(); 
		Object other$name = other.getName(); 
		return this$name == null ? other$name == null : this$name.equals(other$name); 
	} 
	public int hashCode() { 
		int PRIME = 59; 
		int result = 1; 
		result = result * 59 + getAge(); 
		Object $name = getName(); 
		result = result * 59 + ($name == null ? 43 : $name.hashCode()); 
		return result; 
	} 
	public String toString() { 
		return "User(name=" + getName() + ", age=" + getAge() + ")"; 
	} 
}

10、Builder

@Builder注解会生成类的复杂的构建器API,此注解可以用于类、构造函数或其他方法上。

@Builder
@ToString
public class User {
	private String name;
	private int age;
	private String gender;
}

编译后还会生成一个内部类UserBuilder:

public class User
{
	private String name;
	private int age;
	private String gender;

	User(String name, int age, String gender)
	{
		this.name = name; this.age = age; this.gender = gender; 
	} 
	public static UserBuilder builder() { 
		return new UserBuilder(); 
	} 
	public String toString() { 
		return "User(name=" + this.name + ", age=" + this.age + ", gender=" + this.gender + ")"; 
	}


	public static class UserBuilder
	{
		private String name;
		private int age;
		private String gender;

		public UserBuilder name(String name)
		{
			this.name = name; 
			return this; 
		} 
		public UserBuilder age(int age) { 
			this.age = age; 
			return this; 
		} 
		public UserBuilder gender(String gender) { 
			this.gender = gender; 
			return this; 
		} 
		public User build() { 
			return new User(this.name, this.age, this.gender); 
		} 
		public String toString() { 
			return "User.UserBuilder(name=" + this.name + ", age=" + this.age + ", gender=" + this.gender + ")"; 
		}
	}
}

测试代码:

public static void main(String[] args) {
	User user = User.builder().name("albert").age(30).gender("male").build();
	System.out.println(user);
}

输出:

User(name=albert, age=30, gender=male)

11、Synchronized

@Synchronized是synchronized方法修饰符的更安全变体,和synchronized类似,此注解只能用于静态方法和实例方法。与synchronized不一样的是锁定对象不同,synchronized关键字锁定的是this,而注解锁定的是一个私有的$lock字段。如果该字段不存在则会创建;如果是静态方法,则会创建一个静态的$LOCK字段。

public class App {

	@Synchronized
	public void hello(){
		System.out.println("hello world!");
	}
	
	@Synchronized
	public static void welcome(){
		System.out.println("hi");
	}
}

编译后:

public class App
{
	private final Object $lock = new Object[0]; 
	private static final Object $LOCK = new Object[0];

	public void hello() {
		synchronized (this.$lock) {
			System.out.println("hello world!");
		}
	}

	public static void welcome() { 
		synchronized ($LOCK) {
			System.out.println("hi");
		}
	}
}

如果需要,可以自己创建这些锁,并在使用注解时指定:

public class App {

	private final Object readLock = new Object();
	
	@Synchronized("readLock")
	public void foo(){
		System.out.println("bar");
	}
}

编译后:

public class App
{
	private final Object readLock = new Object();

	public void foo() {
		synchronized (this.readLock) {
			System.out.println("bar");
		}
	}
}

12、With

@With注解会创建一个修改某个字段的克隆方法,此注解需要全参构造函数。

@AllArgsConstructor
public class User {
	@With
	private String name;
	private int age;
}

编译后:

public class User
{
	private String name;
	private int age;

	public User withName(String name)
	{
		return this.name == name ? this : new User(name, this.age);
	}

	public User(String name, int age)
	{
		this.name = name; this.age = age;
	}
}

13、Log

在类上使用@Log注解,lombok会自动生成一个static final类型的logger字段;该字段按照使用的日志框架的常用方式进行初始化。

支持以下几种:

  • @CommonsLog
private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class); 
  • @Flogger
private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass(); 
  • @JBossLog
private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class); 
  • @Log
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName()); 
  • @Log4j
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class); 
  • @Log4j2
private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class); 
  • @Slf4j
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class); 
  • @XSlf4j
private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class); 
  • @CustomLog

自定义日志还需要在lombok.config文件中添加配置。

private static final com.foo.your.Logger log = com.foo.your.LoggerFactory.createYourLogger(LogExample.class); 
  • 样例
@Log
public class App {

	public static void main(String[] args) {
		log.info("do something");
	}
}

编译后:

public class App
{
	private static final Logger log = Logger.getLogger(App.class.getName());

	public static void main(String[] args)
	{
		log.info("do something");
	}
}

运行程序输出:

十月 15, 2023 16:15:30 下午 com.lombok.App main
信息: do something
参考资料

Lombok features

Eclipse, Spring Tool Suite, (Red Hat) JBoss Developer Studio, MyEclipse