關于Lombok簡化編碼使用及說明
1.Lombok介紹以及其它簡介
1.1 介紹
Lombok是一個通過注解的形式或簡單關鍵字簡化和消除Java應用程序中一些必須但是重復或顯得臃腫的樣板代碼的實用工具,使用Lombok會在編譯階段根據相應的注解生成對應的字節(jié)碼,使編譯前的源碼看起來更加簡潔,但功能不變。
其特征可參見官網
1.2 優(yōu)點
優(yōu)點有很多,這里主要列舉自己認為重要的
- 提高編碼效率
- 使代碼更簡潔
- 消除冗長代碼
- 避免修改字段名字時忘記修改方法名
1.3 原理
1.為什么能使用Lombok?
Lombok支持JSR 269 Pluggable Annotation Processing API,Javac從Java6開始支持“JSR 269 API”規(guī)范,只要程序實現了該API,就能在Javac運行的時候得到調用
2.Javac編譯源碼的具體流程如下:

1.4 安裝
Lombok需要通過插件的形式與IDE集成,如果使用IntelliJ IDEA可直接到插件倉庫搜索Lombok進行安裝,最后重啟IDEA。
如圖:

1.5 類構造
| 注解(位置TYPE)/屬性 | staticName私有化生產的構造器并生成一個靜態(tài)構造方法 | onConstructor在生成的構造器上增加其他注解 | access設置生成的構造器的可見性 | force為true時,初始化所有靜態(tài)屬性到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 是靜態(tài)的 |
| @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 日志
| 注解(位置) | 日志類類型 |
|---|---|
| @CommonsLog | org.apache.commons.logging.Log |
| @Log | java.util.logging.Logger |
| @JBossLog | org.jboss.logging.Logger |
| @Log4j | org.apache.log4j.Logger |
| @Log4j2 | org.apache.logging.log4j.Logger |
| @Slf4j | org.slf4j.Logger |
| @Slf4j2 | org.slf4j.ext.XLogger |
關于日志上的注解,重點應該放在日志類型的選擇上。一般情況下優(yōu)先使用@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,區(qū)別在于字段會轉換為final類型,且沒有setter方法 |
| @NonFinal | 字段、方法、方法參數、本地變量、注解、接口、類、枚舉 | 用來取消因使用@FieldDefaults和@Value而加上的final修飾符 |
| @SneakyThrows | 方法、構造函數 | 粗粒度的try-catch,等同于try-catch 捕獲異常 |
| @Synchronized | 方法 | 作用等同于synchronized關鍵字,可自定義鎖對象 |
| @Log4j / @Slf4j / @Log | 接口、類、枚舉、注解 | 簡化定義日志記錄器對象的代碼,根據日志框架的不同選擇不同的Log注解 |
| @Tolerate | 方法、注解 | 解決某些情況下使用Lombok注解生成的構造器或方法與開發(fā)者自己寫構造器或方法因為沖突而被跳過的情況 |
| @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關鍵字類似,同樣起到本地類型推斷的作用,區(qū)別在于var修飾的變量不會轉變?yōu)閒inal類型,而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
用來簡化資源清理回收的代碼,確保指定的資源在退出當前代碼執(zhí)行范圍前進行自動清理,消除常見的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方法,默認情況下使用的是所有非靜態(tài)字段,也可以使用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類似,區(qū)別在于如果變量不加@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關鍵字,區(qū)別在于鎖對象不同,對于synchronized關鍵字,修飾類方法時鎖對象是class對象,修飾成員方法時鎖對象是this對象,而使用@synchronized注解時鎖對象分別是私有靜態(tài)變量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注解生成的構造器或方法與開發(fā)者自己寫構造器或方法因為沖突而被跳過的情況,將@Tolerate修飾在構造器/方法上,會被lombok視為該構造器/方法不存在,典型的如當@Data和@Builder同時使用時Lombok生成構造器只有一個包含所有成員屬性的構造函數,如果再自定義一個無參構造函數將會沖突,此時可以使用@Tolerate解決
@Data
@Builder
public class TolerateExample {
private String name;
private String age;
@Tolerate
public TolerateExample() {
}
}
2.1.17 @UtilityClass
創(chuàng)建工具類的注釋,當在類上加上該注解,該類會被修飾為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的常規(guī)認知或者只支持部分開發(fā)環(huán)境,所以不推薦使用
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,并帶有附加警告。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
idea中一鍵自動生成序列化serialVersionUID方式
這篇文章主要介紹了idea中一鍵自動生成序列化serialVersionUID方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09
Spring Boot項目添加外部Jar包以及配置多數據源的完整步驟
這篇文章主要給大家介紹了關于Spring Boot項目添加外部Jar包以及配置多數據源的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2020-06-06
使用idea開發(fā)javaWeb應用程序的思路(實現用戶的增刪改查)
這篇文章主要介紹了使用idea開發(fā)javaWeb應用程序的思路(實現用戶的增刪改查),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01

