关于Lombok简化编码使用及说明

这篇文章主要介绍了关于Lombok简化编码使用及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

1.Lombok介绍以及其它简介

1.1 介绍

Lombok是一个通过注解的形式或简单关键字简化和消除Java应用程序中一些必须但是重复或显得臃肿的样板代码的实用工具,使用Lombok会在编译阶段根据相应的注解生成对应的字节码,使编译前的源码看起来更加简洁,但功能不变。

其特征可参见官网

1.2 优点

优点有很多,这里主要列举自己认为重要的

  • 提高编码效率
  • 使代码更简洁
  • 消除冗长代码
  • 避免修改字段名字时忘记修改方法名

1.3 原理

1.为什么能使用Lombok?

Lombok支持JSR 269 Pluggable Annotation Processing API,Javac从Java6开始支持“JSR 269 API”规范,只要程序实现了该API,就能在Javac运行的时候得到调用

2.Javac编译源码的具体流程如下:

1.4 安装

Lombok需要通过插件的形式与IDE集成,如果使用IntelliJ IDEA可直接到插件仓库搜索Lombok进行安装,最后重启IDEA。

如图:

1.5 类构造

注解(位置TYPE)/属性staticName私有化生产的构造器并生成一个静态构造方法onConstructor在生成的构造器上增加其他注解access设置生成的构造器的可见性force为true时,初始化所有静态属性到0/false/null
@AllArgsConstructor生成一个全参构造器 
@RequiredArgsConstructor生成一个必要参数构造器 
@NoArgsConstructor生成一个空参构造器
注解(位置)属性
@Builder(TYPE,METHOD, CONSTRUCTOR)生成目标类的一个建造者内部类builderClassName:自定义建造类 (TypeName)Builder 的类名builderMethodName:自定义 builder() 的方法名buildMethodName:自定义 build() 的方法名toBuilder
@Builder.ObtainVia(FIELD,PARAMETER)注明一个参数或属性的获取方式field:使用 this.field 赋值method:使用this.method() 赋值isStatic:使用 SelfType.method(this) 赋值,要求 mthod 是静态的
@Builder.Default(FIELD)注明一个属性有默认值 
@Singular(FIELD,PARAMETER)允许为集合类型的属性一项一项赋值value:单项赋值方法的方法名

1.6 类属性

注解(位置)属性
@Getter(TYPE,FIELD)自动生成标准的getter方法lazy:默认false,不可用onMethod:在方法上增加其他注解value:设置方法的可见性
@Setter(TYPE,FIELD)自动生成标准的setter方法onParam:在方法参数上增加其他注解onMethod:同@Getter.onMethodvalue:同@Getter.value
@Data(TYPE)@Getter/@Setter,@ToString, @EqualsAndHashCode和@RequiredArgsConstructor 组合的便捷写法staticConstructor:同@RequiredArgsConstructor.staticName
@EqualsAndHashCode(TYPE)利用父类方法和相关属性生成equals()和hashCode()callSuper:在使用本类属性前先使用父类方法计算doNotUseGetters:不使用getter方法exclude:计算时不使用这里罗列的属性of:计算时使用这里罗列的属性onParam:同@Setter.onParam
@ToString(TYPE)利用父类方法和相关属性生成callSuper:同@EqualsAndHashCode.callSuperdoNotUseGetters:同@EqualsAndHashCode.doNotUseGettersexclude:同@EqualsAndHashCode.excludeof:同@EqualsAndHashCode.ofincludeFieldNames:是否打印属性名

1.7 日志

注解(位置)日志类类型
@CommonsLogorg.apache.commons.logging.Log
@Logjava.util.logging.Logger
@JBossLogorg.jboss.logging.Logger
@Log4jorg.apache.log4j.Logger
@Log4j2org.apache.logging.log4j.Logger
@Slf4jorg.slf4j.Logger
@Slf4j2org.slf4j.ext.XLogger

关于日志上的注解,重点应该放在日志类型的选择上。一般情况下优先使用@Slf4j或@Slf4j2

1.8 杂项

注解(位置)日志类类型
@NonNull(FIELD,METHOD,PARAMETER,LOCAL_VARIABLE)自动生成引用空检查代码,为空时抛出空指针异常 
@SneakThrows(METHOD,CONSTRUCTOR)自动生成代码,抛出受检异常value:需要向上抛出的异常类型
@Synchronized(METHOD)被标注的方法使用生成的锁对象(lock和Lock)而非默认的(this和SlefType)value:使用指定的属性作为锁对象
@Value(TYPE)便捷地转化可变类到不可变staticConstructor:同@RequiredArgsConstructor.staticName
@Cleanup(LOCAL_VARIABLE)生成自动关闭资源的代码value:使用指定的方法关闭资源,默认使用close()
  • val 便捷地声明final局部变量的类型(主要用于含有类型推断的情况)
  • var val的非final情况

除了上述提到的注解和类,在 lombok.experimental 中还包含一些处于实验阶段中的注解(例如自动生成代理方法的@Delegate等)和类,这里不再描述。

2. 使用Lombok

要在项目中使用Lombok,首先要在项目中引入lombok的依赖(使用Maven引入),重新编译源代码

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.20</version>
    <scope>provided</scope>
</dependency>

可根据项目自行选择jar包版本:下载地址

IDEA中使用Lombok的注意事项:

  • 项目中要使用Lombok不仅IDEA要支持(否则一堆错误),项目中也要引入jar包
  • 如果配置lombok.config文件,修改文件的属性值后,并不会自动重新编译class文件,IDEA编辑器也不会自动更新,所有每次修改配置文件后最后关闭java文件窗口重新打开,并且clean下项目

2.1 常用注解

注解/关键字可使用位置说明
val局部变量简化局部变量声明的类型
@NonNull字段、方法、入参、本地变量生成检查NullPointException代码
@Cleanup可关闭资源的本地变量对象,且销毁方法没有参数简化资源清理回收的代码,消除try-catch-finally代码块
@Getter / @Setter字段、枚举常量、接口、类、枚举、注解简化getter、setter代码
@ToString接口、类、枚举、注解自动生成toString方法(可以添加排除和依赖)
@EqualsAndHashCode接口、类、枚举、注解自动生成equals方法和hashCode方法
@NoArgsConstructor接口、类、枚举、注解生成无参构造函数
@RequiredArgsConstructo接口、类、枚举、注解生成所有标识为@NonNull的成员属性的构造函数
@AllArgsConstructor接口、类、枚举、注解生成包含所有成员属性的构造函数
@Data接口、类、枚举、注解是@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstructor的组合效果
@Value接口、类、枚举、注解类似于@Data,区别在于字段会转换为final类型,且没有setter方法
@NonFinal字段、方法、方法参数、本地变量、注解、接口、类、枚举用来取消因使用@FieldDefaults和@Value而加上的final修饰符
@SneakyThrows方法、构造函数粗粒度的try-catch,等同于try-catch 捕获异常
@Synchronized方法作用等同于synchronized关键字,可自定义锁对象
@Log4j / @Slf4j / @Log接口、类、枚举、注解简化定义日志记录器对象的代码,根据日志框架的不同选择不同的Log注解
@Tolerate方法、注解解决某些情况下使用Lombok注解生成的构造器或方法与开发者自己写构造器或方法因为冲突而被跳过的情况
@Builder会按builder模式生成一个内部类

2.1.1 val / var

val用来简化局部变量声明的类型,与Java10中的var关键字类似,都是从初始化表达式中推断出变量的声明类型,起到本地类型推断的作用。需要注意的是val修饰的变量都会变成final类型,其引用不可更改。

val example = new ArrayList<String>();
example.add("hello");
example.add("lombok");
val element = example.get(0);

等价于:

final ArrayList<String> example = new ArrayList<String>();
example.add("hello");
example.add("lombok");
final String element = example.get(0);

注意:

  • 1.只能在本地变量声明的时候使用,不可在类的字段上使用
  • 2.val修饰的变量本身是final类型的,不能被修改

var与val关键字类似,同样起到本地类型推断的作用,区别在于var修饰的变量不会转变为final类型,而val修饰的变量都会变成final类型

2.1.2 @NonNull

常用于加在方法和构造函数的入参上,它会帮助我们生成检查NullPointerException的代码

public NonNullExample(@NonNull Person person) {
    this.name = person.getName();
}

等价于:

public NonNullExample(@NonNull Person person) {
    if(person == null) {
        throw new NullPointException("person");
    }
    this.name = person.getName();
}

2.1.3 @Cleanup

用来简化资源清理回收的代码,确保指定的资源在退出当前代码执行范围前进行自动清理,消除常见的try-catch-finally代码样板,作用等同于try-with-resource,不过需要注意@Cleanup只能指定没有参数的资源销毁方法,如果销毁方法有入参则不能使用@Cleanup注解

public static void tradition() {
		InputStream in = null;
		OutputStream out = null;
		try {
			in = new FileInputStream("test.txt");
			out = new FileOutputStream("output.txt");
			byte[] buffer = new byte[1024];
			int begin = 0;
			while (true) {
				int len = in.read(buffer);
				if (len == -1)
					break;
				out.write(buffer, begin, len);
				begin += len;
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (out != null) {
				try {
					out.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	public static void tryWithResource() {
		try (InputStream in = new FileInputStream("test.txt"); 
			OutputStream out = new FileOutputStream("output.txt")) {
			byte[] buffer = new byte[1024];
			int begin = 0;
			while (true) {
				int len = in.read(buffer);
				if (len == -1)
					break;
				out.write(buffer, begin, len);
				begin += len;
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public static void cleanUp() {
		try {
			@Cleanup InputStream in = new FileInputStream("test.txt");
			@Cleanup OutputStream out = new FileOutputStream("output.txt");
			byte[] buffer = new byte[1024];
			int begin = 0;
			while (true) {
				int len = in.read(buffer);
				if (len == -1)
					break;
				out.write(buffer, begin, len);
				begin += len;
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

2.1.4 @Getter / @Setter

分别用来简化getter和setter样板代码,默认生成的getter、setter方法修饰符为public,如果需要指定方法的访问范围,可以设置AccessLevel属性,如:

@Getter 
@Setter(AccessLevel.PROTECTED) 
private String password;

另外,@Getter注解还有一个lazy=true的属性,设置了该属性会使我们调用getter方法时才真正去计算获取到的值,并且将第一次计算后的结果缓存下来,之后的调用直接返回该缓存值

@Getter(lazy = true)
private final double[] cached = expensive();
private double[] expensive() {
	long begin = System.currentTimeMillis();
	double[] result = new double[5];
	for (int i = 0; i < result.length; i++) {
		result[i] = Math.asin(i);
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	System.out.println((System.currentTimeMillis() - begin) / 1000);
	return result;
}
public static void main(String[] args) {
	GetterLazyExample example = new GetterLazyExample();
	System.out.println(example.getCached());
	System.out.println(example.getCached());
}

等价于:

private final AtomicReference<Object> cached = new AtomicReference<>();
public double[] getCached() {
	Object value = this.cached.get();
	if (value == null) {
		synchronized (this.cached) {
			value = this.cached.get();
			if (value == null) {
				final double[] actualValue = expensive();
				value = actualValue == null ? this.cached : actualValue;
				this.cached.set(value);
			}
		}
	}
	return (double[]) (value == this.cached ? null : value);
}
private double[] expensive() {
	long begin = System.currentTimeMillis();
	double[] result = new double[5];
	for (int i = 0; i < result.length; i++) {
		result[i] = Math.asin(i);
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	System.out.println((System.currentTimeMillis() - begin) / 1000);
	return result;
}
public static void main(String[] args) {
	GetterLazyExample_Src example = new GetterLazyExample_Src();
	System.out.println(example.getCached());
	System.out.println(example.getCached());
}

2.1.5 @ToString

用来自动生成toString方法,默认的toString方法会打印出类名和字段属性和值,如果需要排除指定字段可以用exclude='字段名’的方式进行排除;如果要嵌套调用父类的toString方法,则加上callSuper=true,includeFieldNames=true等属性

// @ToString // 默认打印类名、每个字段名=值,用逗号分隔
// @ToString(exclude="password") //exclude属性指定排除哪些字段
@ToString(callSuper = true,includeFieldNames=true)
public class ToStringExample extends Parent {
	@Getter
	@Setter
	private String name;
	
	@Getter
	@Setter
	private String password;
	
	@Getter
	@Setter
	private int age;
	public static void main(String[] args) {
		System.out.println(new ToStringExample());
	}
}
@ToString
class Parent {
	@Getter
	@Setter
	private String address;
	
	@Getter
	@Setter
	private String city;
}

2.1.6 @EqualsAndHashCode

用来从字段中自动生成equals和hashCode方法,默认情况下使用的是所有非静态字段,也可以使用exclude属性排除指定的字段

@EqualsAndHashCode(exclude= {"name"})
public class EqualsAndHashCodeExample {
	@Getter 
	@Setter 
	private String name;
	
	@Getter 
	@Setter 
	private int age;
		
	@Getter 
	@Setter 
	private double weight;
	public static void main(String[] args) {
		EqualsAndHashCodeExample example1 = new EqualsAndHashCodeExample();
		example1.setName("小明");
		example1.setAge(10);
		
		EqualsAndHashCodeExample example2 = new EqualsAndHashCodeExample();
		
		example2.setName("小红");
		example2.setAge(10);
		
		System.out.println(example1.hashCode());
		System.out.println(example2.hashCode());
		System.out.println(example1.equals(example2));
	}
}

2.1.6 @NoArgsConstructor

用来生成无参构造函数,如果类含有final字段,会出现编译错误,通过指定属性force为true,为final字段进行初始化

@NoArgsConstructor
public class NoArgsConstructorExample {
	@Getter 
	@Setter 
	private String name;
}

等价于:

public class NoArgsConstructorExample {
    private String name;
    public NoArgsConstructorExample() {
        //public无参构造器
    }
    //省略getter、setter方法
    ......
}

2.1.7 @RequiredArgsConstructor

用来生成包含所有修饰为@NonNull的成员属性的构造函数

@RequiredArgsConstructor
public class RequiredArgsConstructorExample {
	@Getter 
	@Setter 
	@NonNull 
	private String name;
	
	@Getter 
	@Setter 
	private String password;
	
	@Getter 
	@Setter 
	@NonNull
	private Character sex;
}

等价于:

public class RequiredArgsConstructorExample {
    private String name;
    private String password;
    private Character sex;
    
    private RequiredArgsConstructorExample(String name, Character sex) {
        if(name == null) {
            throw new NullPointerException("name");
        }
        if(sex == null) {
            throw new NullPointerException("sex");
        }
        this.name = name;
        this.sex = sex;
    }
    //省略getter、setter方法
    ......
}

2.1.8 @AllArgsConstructor

@AllArgsConstructor
public class AllArgsContructorExample {
	@Getter 
	@Setter 
	private String name;
	
	@Getter 
	@Setter 
	private Integer age;
	
	@Getter 
	@Setter 
	private String address;
}

等价于:

public class AllArgsContructorExample {
    private String name;
    private Integer age;
    private String address;
    
    public AllArgsContructorExample(String name, Integer age, String address) {
        this.name = name,
        this.age = age;
        this.address = address;
    }
    //省略getter、setter方法
    ......
}

2.1.9 @Data

是一个简单粗暴的组合注解,使用@Data注解相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstructor这几个注解

@Data
public class DataExample {
	private String name;
	private int age;
	private String password;
}

2.1.10 @Value

跟@Data类似,区别在于如果变量不加@NonFinal修饰,@Value会将字段变成final类型,同时也没有setter方法

2.1.11 @NonFinal

修饰字段,用来取消因使用@FieldDefaults和@Value而加上的final修饰符

@Value
public class NonFinalExample {
	private String id; //final
	
	private String name; //final
	
	@NonFinal 
	private String password; //非final
}

2.1.12 @Builder

简化了普通的建造者模式API,可以用在类、构造器、方法上,如果字段属于集合类型,加上@Singular,会生成两个向集合中添加单一元素和所有元素的方法,以及一个清除集合的方法

@Builder
 public class Example {
        private int foo;
        private final String bar;
 }

等价于:

public class Example<T> {
    private T foo;
    private final String bar;
    private Example(T foo, String bar) {
            this.foo = foo;
            this.bar = bar;
    }
    public static <T> ExampleBuilder<T> builder() {
            return new ExampleBuilder<T>();
    }
    public static class ExampleBuilder<T> {
            private T foo;
            private String bar;
            private ExampleBuilder() {}
            public ExampleBuilder foo(T foo) {
                    this.foo = foo;
                    return this;
            }
            public ExampleBuilder bar(String bar) {
                    this.bar = bar;
                    return this;
            }
            @java.lang.Override 
            public String toString() {
                    return "ExampleBuilder(foo = " + foo + ", bar = " + bar + ")";
            }
            public Example build() {
                    return new Example(foo, bar);
            }
    }
}

2.1.13 @SneakyThrows

注解用在方法和构造函数上,它会将方法中的所有代码用try-catch语句包裹起来,当捕获到异常后通过Lombok.sneakyThrow(e)将原始异常抛出,不过需要注意的是调用该方法的Client端并不知道会抛出哪种异常,即使这是一个CheckException

public class SneakyThrowsExample {
	@SneakyThrows(UnsupportedEncodingException.class)
	public static String utf8ToString(byte[] bytes) {
		return new String(bytes, "UTF-8");
	}
	
	public static void main(String[] args) {
		String str = SneakyThrowsExample.utf8ToString("hello lomboks".getBytes());
		System.out.println(str);
	}
}

2.1.14 @Synchronized

注解用在方法上,作用等同于synchronized关键字,区别在于锁对象不同,对于synchronized关键字,修饰类方法时锁对象是class对象,修饰成员方法时锁对象是this对象,而使用@synchronized注解时锁对象分别是私有静态变量LOCK和私有final对象lock,也可以自己指定锁对象

public class SynchronizedExample {
	private final Object readLock = new Object();
	
	@Synchronized("readLock")
	@SneakyThrows
	public void read() {
		System.out.println(Thread.currentThread().getName() + " read");
		Thread.sleep(3000);
	}
	
	public static void main(String[] args) {
		SynchronizedExample example = new SynchronizedExample();
		new Thread(()->example.read()).start();
		new Thread(()->example.read()).start();
	}
}

2.1.15 Log注解:@CommonsLog、@Log、@Log4j、@Log4j2、@Slf4j、@XSl4j、@JBossLog

Log注解可以省去从日志工厂生成日志记录器对象的代码,可以使用topic指定生成log对象时的类名,根据项目中使用的日志框架不同,有不同的注解可以选择

@CommonsLog(topic="LogExample")
//等价于
org.apache.commons.logging.LogFactory.getLog(LogExample.class);
@Log(topic="LogExample")
//等价于
java.util.loggin.Logger.getLogger(LogExample.class);
@Log4j(topic="LogExample")
//等价于
org.apache.log4j.Logger.getLogger(LogExample.class);
@Log4j2(topic="LogExample")
//等价于
org.apache.loggin.log4j.LogManager.getLoggerr(LogExample.class);
@Slf4j(topic="LogExample")
//等价于
org.slf4j.LoggerFactory.getLogger(LogExample.class);
@XSLf4j(topic="LogExample") 
//等价于
org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
@JBossLog(topic="LogExample") 
//等价于
org.jboss.logging.Logger.getLogger(LogExample.class);

2.1.16 @Tolerate

该注解用来解决某些情况下使用Lombok注解生成的构造器或方法与开发者自己写构造器或方法因为冲突而被跳过的情况,将@Tolerate修饰在构造器/方法上,会被lombok视为该构造器/方法不存在,典型的如当@Data和@Builder同时使用时Lombok生成构造器只有一个包含所有成员属性的构造函数,如果再自定义一个无参构造函数将会冲突,此时可以使用@Tolerate解决

@Data
@Builder
public class TolerateExample {
	private String name;
	private String age;
	@Tolerate
	public TolerateExample() {
	}
}

2.1.17 @UtilityClass

创建工具类的注释,当在类上加上该注解,该类会被修饰为final类型,如果该类声明了构造函数编译器将会提示错误,否则会自动生成一个私有的构造函数,内部抛出一个UnsupportedOperationException异常。并且所有的方法、内部类和属性都会被修饰为static

@UtilityClass
public class UtilityClassExample {
	private DateFormat df = new SimpleDateFormat("YYYY-MM-DD");
	
	public String formateToDay(Date date) {
		return df.format(date);
	}
}

等价于:

public class UtilityClassExample {
	private static DateFormat df = new SimpleDateFormat("YYYY-MM-DD");
	
	public static String formateToDay(Date date) {
		return df.format(date);
	}
	
	private UtilityClassExample() {
		throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
	}
	
}

除了以上常用的基本功能外,Lombok还有部分实验性质的特性没有正式推荐使用,有些可能违背了对Java的常规认知或者只支持部分开发环境,所以不推荐使用

2.2 扩展

2.2.1 lombok.config增加

lombok.equalsAndHashCode.doNotUseGetters = [true | false] (default:false)

如果设置为 true,lombok将直接访问字段,而不是在生成equals和hashcode方法时使用getter(如果可用),可以在该注解上配置属性 donotusegetter 来标示不使用getter的字段,这样可以覆盖默认配置

lombok.equalsAndHashCode.callSuper = [call | skip | warn] (default:warn)

如果设置为 call,lombok将生成对hashCode的超类实现的调用

如果设置为 skip,则不会生成这样的调用,默认行为 warn 类似于 skip,并带有附加警告。 

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程学习网。 

本文标题为:关于Lombok简化编码使用及说明

基础教程推荐