Lombok
是什么
Lombok是一个通过注解的形式或简单关键字简化和消除Java应用程序中一些必须但是重复或显得臃肿的样板代码的实用工具,使用Lombok会在编译阶段根据相应的注解生成对应的字节码,使编译前的源码看起来更加简洁,但功能不变。
缺点是增加了依赖和学习Lombok的成本,还有一定程度上对代码的可读性造成影响。
怎么用
-
安装:Lombok需要通过插件的形式与IDE集成,如果使用IntelliJ IDEA可直接到插件仓库搜索Lombok进行安装,如果使用Eclipse,首先需要下载lombok.jar(https://www.projectlombok.org/download),然后在CMD下执行命令:
java -jar lombok.jar
,等待扫描出本机安装的eclipse后进行安装确认,最后重启eclipse即可。 还有一种安装方式在直接拷贝lombok.jar到eclipse.ini的同级目录下,然后编辑eclipse.ini,在最后一行加上-javaagent:lombok.jar
,最后重启eclipse。 -
使用:要在项目中使用Lombok,首先要在项目中引入lombok的依赖,重新编译源代码。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
基本特性
注解/关键字 | 可使用位置 | 说明 |
---|---|---|
val | 局部变量 | 简化局部变量声明的类型 |
@NonNull | 字段、方法、入参、本地变量 | 生成检查NullPointException代码 |
@Cleanup | 可关闭资源的本地变量对象,且销毁方法没有参数 | 简化资源清理回收的代码,消除try-catch-finally代码块 |
@Getter/@Setter | 字段、枚举常量、接口、类、枚举、注解 | 简化getter、setter代码 |
@ToString | 接口、类、枚举、注解 | 自动生成toString方法 |
@EqualsAndHashCode | 接口、类、枚举、注解 | 自动生成equals方法和hashCode方法 |
@NoArgsConstructor | 接口、类、枚举、注解 | 生成无参构造函数 |
@RequiredArgsConstructor | 接口、类、枚举、注解 | 生成所有标识为@NonNull的成员属性的构造函数 |
@AllArgsConstructor | 接口、类、枚举、注解 | 生成包含所有成员属性的构造函数 |
@Data | 接口、类、枚举、注解 | 是@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstructor的组合效果 |
@Value | 接口、类、枚举、注解 | 类似于@Data,区别在于字段会转换为final类型,且没有setter方法 |
@NonFinal | 字段、方法、方法参数、本地变量、注解、接口、类、枚举 | 用来取消因使用@FieldDefaults和@Value而加上的final修饰符 |
@SneakyThrows | 方法、构造函数 | 粗粒度的try-catch |
@Synchronized | 方法 | 作用等同于synchronized关键字,可自定义锁对象 |
@Log | 接口、类、枚举、注解 | 简化定义日志记录器对象的代码,根据日志框架的不同选择不同的Log注解 |
- val
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);
- @NonNull
@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();
}
- @Cleanup
@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();
}
}
- @Getter/@Setter
@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());
}
- @ToString
@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;
}
- @EqualsAndHashCode
@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));
}
}
- @NoArgsConstructor/@RequiredArgsConstructor/@AllArgsConstructor
@NoArgsConstructor用来生成无参构造函数。
@NoArgsConstructor
public class NoArgsConstructorExample {
@Getter @Setter private String name;
}
等价于:
public class NoArgsConstructorExample {
private String name;
public NoArgsConstructorExample() {
//public无参构造器
}
//省略getter、setter方法
......
}
@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方法
......
}
@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方法
......
}
- @Data
@Data是一个简单粗暴的组合注解,使用@Data注解相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstructor这几个注解
@Data
public class DataExample {
private String name;
private int age;
private String password;
}
- @Value
@Value跟@Data类似,区别在于如果变量不加@NonFinal修饰,@Value会将字段变成final类型,同时也没有setter方法。
- @NonFinal
修饰字段,用来取消因使用@FieldDefaults和@Value而加上的final修饰符。
@Value
public class NonFinalExample {
private String id; //final
private String name; //final
@NonFinal private String password; //非final
}
- @Builder
@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);
}
}
}
- @SneakyThrows
@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);
}
}
- @Synchronized
@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();
}
}
- 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);
实验性特性
除了以上常用的基本功能外,Lombok还有部分实验性质的特性没有正式推荐使用,有些可能违背了对Java的常规认知或者只支持部分开发环境,所以不推荐使用。
- @Accessors
@Accessors是一个为getter和setter设计的更为流畅的API,当设置了chain=true属性,会自动生成符合链式调用的setter方法,返回this引用代替默认的void方法,如果设置flunt=true,则不会有get/set前缀,同样支持链式调用。
@Accessors(chain=true)
@Data
public class AccessorsExample {
private String id;
private String name;
private String address;
public static void main(String[] args) {
AccessorsExample example = new AccessorsExample();
example.setId("123").setName("小明").setAddress("深圳");
}
}
- @Delegate
@Delegate能够将一个对象的某些方法代理给另一个对象。
public class DelegateExample {
private interface DelegateMethod { //该接口的方法必须是collection拥有的方法
boolean add(String item);
int size();
}
@Delegate(types=DelegateMethod.class)
//声明代理的接口,即将collection的某些方法代理给当前对象
private final Collection<String> collection = new ArrayList<>();
public static void main(String[] args) {
DelegateExample example = new DelegateExample();
example.add("element"); //实际上添加到了collection中
System.out.println(example.size());
}
}
- @ExtensionMethod
@ExtensionMethod可以Wie已经存在的类增加方法,但是这种方式在代码风格上冲击较大、在运行时没有好的办法让其他类引用,部分开发环境如netbeans下不可用等等,因此不建议使用。
@Data
@ExtensionMethod({DateUtils.class}) //扩展的方法来自DateUtils类
public class ExtensionMehtodExample {
public void test() {
Date date = new Date();
String str = date.formate();
System.out.println(str);
}
public static void main(String[] args) {
new ExtensionMehtodExample().test();
}
}
class DateUtils {
public static final String formate(Date date) { //必须是static方法
DateFormat formator = new SimpleDateFormat("HH:mm:ss");
return formator.format(date);
}
}
- @FieldDefaults
设置默认的字段修饰符,即如果字段不加修饰符则采用FieldDefaults注解设置的修饰符.
@FieldDefaults(level = AccessLevel.PRIVATE)
@Data
public class FieldDefaultsExample {
String id;
String name;
protected String address;
}
- @Helper
修饰在包含局部类的方法上,用来指示将局部类中的所有方法暴露给外围方法,就好像它们是其辅助方法一样。
public class HelperExample {
public void doSomething(String name) {
@Helper // 修饰局部类,将局部类中的所有方法暴露给外围的方法
class Helpers {
void sayHello(String name) {
System.out.println("hello " + name);
}
void sayGoodBye(String name) {
System.out.println("Goodbye " + name);
}
}
sayHello(name); // 直接使用局部类中的方法,等同于new Helpers().sayHello(name);
sayGoodBye(name);
}
public static void main(String[] args) {
new HelperExample().doSomething("小明");
}
}
- PackagePrivate
修饰字段,将当前字段设置为访问权限设置为package-private,一般用来取消因使用@Value和@FieldDefaults而加上的修饰符。
@FieldDefaults(level = AccessLevel.PRIVATE)
@Data
public class PackagePrivateExample {
String id; // public
String name; // public
@PackagePrivate String address; // package-private
}
- @Tolerate
该注解用来解决某些情况下使用Lombok注解生成的构造器或方法与开发者自己写构造器或方法因为冲突而被跳过的情况,将@Tolerate修饰在构造器/方法上,会被lombok视为该构造器/方法不存在,典型的如当@Data和@Builder同时使用时Lombok生成构造器只有一个包含所有成员属性的构造函数,如果再自定义一个无参构造函数将会冲突,此时可以使用@Tolerate解决。
@Data
@Builder
public class TolerateExample {
private String name;
private String age;
@Tolerate
public TolerateExample() {
}
}
- @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");
}
}
- @Wither
使用@Wither修饰字段X,会自动生成一个withX方法,该方法会生成该对象的一个克隆,该注解也可以使用在类上,此时相当于所有非静态字段都加上了@Wither注解。
public class WitherExample {
@Wither private final int foo;
private final String name;
public WitherExample(int foo, String name) {
this.foo = foo;
this.name = name;
}
}
等价于:
public class WitherExample {
private final int foo;
private final String name;
public WitherExample(int foo, String name) {
this.foo = foo;
this.name = name;
}
public WitherExample withFoo(int foo) { //传入的foo相同返回当前对象,否则返回除foo字段不同的一个克隆对象
return this.foo == foo ? this ? new WitherExample(foo, this.name);
}
}
- var
与val关键字类似,同样起到本地类型推断的作用,区别在于var修饰的变量不会转变为final类型,而val修饰的变量都会变成final类型。