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.onMethod value:同@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.callSuper doNotUseGetters:同@EqualsAndHashCode.doNotUseGetters exclude:同@EqualsAndHashCode.exclude of:同@EqualsAndHashCode.of includeFieldNames:是否打印屬性名 |
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 |
關於日誌上的註解,重點應該放在日誌類型的選擇上。一般情況下優先使用@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
,並帶有附加警告。