優雅的Java工具庫Lombok
最近在公司的專案中看到了對於Lombok的應用,通過@Data註解標註POJO,省略了大量的getter/setter程式碼,原先冗長的POJO在瘦身之後直接變得乾淨、清爽,程式設計師再也不需要去關注那些長長的方法,只需要集中注意力於欄位field之中
Lombok簡介
Lombok是一個非常實用的Java工具庫,有效地簡化Java程式碼的冗長。它通過註解如@Data可以直接為Java bean在編譯期動態地生成欄位的getter/setter方法,使用註解@NoArgsConstructor 和@AllArgsConstructor 為Java bean新增無參構造器和有參構造器,甚至可以在Java程式碼中使用val和var宣告一個動態變數,而無需再指定具體的變數型別,區別只是val宣告的變數為final。
Lombok還提供了delombok供生成Javadoc,delombok在執行時會將註解@Data轉換成getter/setter方法,然後移除@Data註解,如果哪天不再需要Lombok,也只需要簡單執行delombok即可。Lombok的構建支援maven和gradle,同時eclipse、myeclipse和idea等主流IDE也都和lombok相容,所以可以放心大膽地使用Lombok,不用擔心IDE的編譯檢查問題。
Lombok栗子
- 引入Lombok依賴
<dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> <version>1.18.2version> <scope>providedscope> dependency>
Lomok註解使用
Lombok的註解分為穩定版本和試驗版本,這裡主要介紹穩定版本,因為試驗版本的支援目前和IDE不是很好
- @Getter/@Setter註解
@Getter/@Setter註解的作用就是為欄位新增getter/setter方法,可標註在類上,也可標註在欄位上。標註在類上表示所有的非靜態(no-static)欄位都會生成相應的getter/setter方法,標註在欄位上表示只為這個欄位生成,且會覆蓋標註在類上的註解。可設定訪問級別,預設為public。@Setter不可以標註final欄位
@[email protected]
public class SetterExample {
@Getter(value=AccessLevel.PRIVATE)
@Setter
private String name;
// [email protected]__({@AnnotationsHere})
@Setter([email protected]__({@Deprecated})) private String age;
//[email protected]__({@AnnotationsHere})
@Setter([email protected]__({})) private String sex;
public static void main(String[] args) {
SetterExample se = new SetterExample();
se.setName("zhangsan");
se.setAge("16");
System.out.println(se.getAge());
System.out.println(se.getName());
}
}
Lombok提供了onX的試驗屬性,分別為:onMethod, onParam, onConstructor,用於向生成的方法,構造器,引數添加註解
反編譯後結果
- @NonNull註解
@NonNull註解標註方法和構造器的引數,如果引數為null,則會丟擲空指標異常,不需要在程式碼中進行null檢測
public class NonNullExample {
@Getter
private String name;
public NonNullExample(@NonNull String name){ this.name = name;
}
public static void main(String[] args){
String name = null;
NonNullExample nne = new NonNullExample(name);
System.out.println(nne.getName());
}
}
- @ToString註解
@ToString註解生成toString()方法
@ToString
public class ToStringExample { @ToString.Exclude private String name;
@ToString.Include
private String age;
private String sex;
public static void main(String[] args) {
ToStringExample tse = new ToStringExample();
System.out.println(tse.toString());
}
}
屬性includeFieldNames,預設為true,包含屬性值
屬性callSuper,預設為false,呼叫父類實現
屬性onlyExplicitlyIncluded,預設為false,僅包含明確包含的屬性
@ToString.Exclude 標註屬性值不包含在toString()方法中
@ToString.Include標註屬性值包含在toString()方法中
- @EqualsAndHashCode
@EqualsAndHashCode註解生成equals()和hashcode()方法,註解的屬性和@ToString類似
@EqualsAndHashCode
public class EqualsAndHashcodeExample { private String name; private String age; private String sex;
public static void main(String[] args) {
EqualsAndHashcodeExample ehe1 = new EqualsAndHashcodeExample();
EqualsAndHashcodeExample ehe2 = new EqualsAndHashcodeExample();
System.out.println(ehe1.equals(ehe2));
System.out.println(ehe1.hashCode());
System.out.println(ehe2.hashCode());
}
}
@NoArgsConstructor @RequiredArgsConstructor @AllArgsConstructor @NoArgsConstructor : 生成一個無引數的構造方法
@NoArgsConstructor(force=true, staticName="newInstance")
public class NoArgsConstructorExample {
//包含的final欄位如果沒有初始化,需要加上force=true強制初始化,否則編譯錯誤
private final String name;
//不會進行null檢查
@NonNull
@Getter
private String age;
private String sex;
public static void main(String[] args) {
NoArgsConstructorExample nace1 = new NoArgsConstructorExample();
System.out.println(nace1.getAge());
NoArgsConstructorExample nace2 = NoArgsConstructorExample.newInstance();
System.out.println(nace2.getAge());
}
}
@RequiredArgsConstructor:會生成一個包含常量,和標識了NotNull的變數 的構造方法。
@RequiredArgsConstructor(staticName="newInstance")
public class RequiredArgsConstructorExample {
private final String name;
@NonNull
@Getter
private String age;
private String sex;
public static void main(String[] args) {
RequiredArgsConstructorExample race1 = new RequiredArgsConstructorExample("lisi", "18");
System.out.println(race1.getAge());
RequiredArgsConstructorExample race2 = RequiredArgsConstructorExample.newInstance("zhangsan", "16");
System.out.println(race2.getAge());
}
}
@AllArgsConstructor:會生成一個包含所有變數,同時如果變數使用了NotNull annotation , 會進行是否為空的校驗
@AllArgsConstructor(staticName="newInstance")
public class AllArgsConstructorExample {
private final String name;
@NonNull
@Getter
private String age;
private String sex;
public static void main(String[] args) {
AllArgsConstructorExample aace1 = new AllArgsConstructorExample("zhangsan", "18", "female");
System.out.println(aace1.getAge());
AllArgsConstructorExample aace2 = AllArgsConstructorExample.newInstance("lisi", "16", "male");
System.out.println(aace2.getAge());
}
}
注意:三個註解生成的構造器都可以指定訪問許可權,同時也可以提供一個靜態方法來供呼叫。三個註解的區別在於對final和@NonNull欄位的處理不同
另外關於staticName屬性,Lombok原始碼註釋如下: If set, the generated constructor will be private, and an additional static ‘constructor’ is generated with the same argument list that wraps the real constructor. 很明顯三個註解都是可以使用構造器直接建立物件的,也可以使用靜態方法建立物件,不知道這段註釋是什麼意思???
- @Data註解
等同於@ToString, @EqualsAndHashcode, @Getter, @Setter和@RequiredArgsConstructor一起使用
- @Value
@Value註解為不可變型別的@Data,是@Data的一個變種。它標註的類和欄位都會被宣告為final
- @Builder註解
@Builder註解為類生成builder api以供呼叫。Builder是一種解決包含數量巨大且繁雜的欄位的類的一種構建方式。
假如一個類有幾十個欄位,那麼該如何設計這個類呢?
方法一:將幾十個欄位都新增在建構函式中。簡單粗暴,而且在建構函式中為欄位初始化也能夠保證物件能夠正確建立。缺點就是幾十個引數只會導致你在建立物件時記錯引數的位置,導致不必要的麻煩。
方法二:依賴注入。Spring的核心功能之一就是依賴注入,藉助這種思想,我們通過無參構造建立一個物件,然後通過setter方法設定必需的屬性。這種方式可以根據需求初始化相關屬性,且邏輯清晰,但也會造成程式碼繁瑣,需要呼叫多次setter方法。
方法三:Builder模式。建造者模式的思想就是將一個大的類的構建分為幾部分建立,從而簡化建立的複雜性。
@Builder
public class BuilderExample {
private String name;
private String age;
private String sex;
public static void main(String[] args) {
BuilderExample be = BuilderExample.builder().name("zhangsan").age("16").sex("male").build();
System.out.println(BuilderExample.builder().name("zhangsan").age("16").sex("male"));
}
}
- @Log
@Log註解為類新增一個日誌物件log,型別為java.util.logging.Logger
這個類有很多變種,詳情如下:
@CommonsLogprivate
static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
@Floggerprivate
static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass();
@JBossLogprivate
static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);
@Logprivate
static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
@Log4jprivate
static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
@Log4j2private
static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
@Slf4jprivate
static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
@XSlf4jprivate
static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
- @CleanUp註解
@CleanUp註解用於關閉資源,呼叫資源的close()方法
public class CleanUpExample {
@SneakyThrows({FileNotFoundException.class, Exception.class})
public static void main(String[] args) {
File file = new File("C:/Users/wang2/Desktop/11.jpg");
@Cleanup
FileInputStream is = new FileInputStream(file);
@Cleanup
FileOutputStream os = new FileOutputStream(new File("C:/Users/wang2/Desktop/111.jpg"));
byte[] buffer = new byte[1024];
int length = 0;
while((length = is.read(buffer)) != -1){
os.write(buffer, 0, length);
}
}
}
注意:丟擲的異常被@SneakyThrows捕獲了
- @SneakyThrows註解
Sneaky的意思是偷偷摸摸地,@SneakyThrows註解的作用就是取代try…catch程式碼塊,自動生成相應的try…catch程式碼塊