Java小工具Lombok安裝和使用,讓JAVA程式碼更優雅
Lombok簡介
Lombok專案通過新增“處理程式”,使java成為一種更為簡單的語言。作為一個Old Java Developer,我們都知道我們經常需要定義一系列的套路,比如定義如下的格式物件。
public class DataExample {
private final String name;
private int age;
private double score;
private String[] tags;
}
我們往往需要定義一系列的Get和Set方法最終展示形式如:
public class DataExample { private final String name; private int age; private double score; private String[] tags; public DataExample(String name) { this.name = name; } public String getName() { return this.name; } void setAge(int age) { this.age = age; } public int getAge() { return this.age; } public void setScore(double score) { this.score = score; } public double getScore() { return this.score; } public String[] getTags() { return this.tags; } public void setTags(String[] tags) { this.tags = tags; } }
當然我們也可以簡化的辦法,第一種就是使用IDEA等IDE提供的一鍵生成的快捷鍵,第二種就是我們今天介紹的 Lombok專案:
public class DataExample { private final String name; (AccessLevel.PACKAGE) private int age; private double score; private String[] tags; }
這樣就可以完成我們的需求,簡直是太棒了,僅僅需要幾個註解,我們就擁有了完整的GetSet方法,還包含了ToString等方法的生成。
Lombok安裝
使用lombok之前需要安裝lombok到IDE,否則IDE無法解析lombok註解;首先去官網下載最新的lonbok安裝包,網址:https://projectlombok.org/download 有兩種安裝方式:
1. 雙擊下載下來的 JAR 包安裝 lombok
安裝之前先關閉eclipse,然後雙擊下載的lombok.jar
如果像上圖所示提示找不到IDE,則需要手動選擇IDE,
出現上面的頁面時,點選Install/Update
開啟 Eclipse 的 About 頁面我們可以看見,如下圖所示,說明安裝成功
2. eclipse 手動安裝 lombok
1) 將 lombok.jar 複製到 eclipse.ini 所在的資料夾目錄下
2) 開啟 eclipse.ini在最後面插入以下兩行並儲存:
-Xbootclasspath/a:lombok.jar
-javaagent:lombok.jar
3) 重啟eclipse
Lombok使用
1、在工程中引入lombok
在pom.xml中加入lombok依賴
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
2、Lombok註解介紹
1、@Data 註解:
包含了:@ToString,@EqualsAndHashCode, @Getter on all fields, @Setter on all non-final fields, [email protected]這些註解
註解在類上;提供類所有屬性的 getting 和 setting 方法,此外還提供了equals、canEqual、hashCode、toString 方法
@Data
public class Person {
private final String name;
private int age;
private double score;
private String[] tags;
}
相當於:
由上圖類結構可以看出,自動添加了set/get方法,同時還有一個帶有final欄位引數的建構函式
public Person(String name) {
this.name = name;
}
還有toString、equals、canEqual、hashCode方法
@Override
public String toString() {
return "DataExample(" + this.getName() + ", " + this.getAge() + ", " + this.g etScore() + ", " + Arrays.deepToString(this.getTags()) + ")";
}
protected boolean canEqual(Object other) {
return other instanceof DataExample;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof DataExample)) return false;
DataExample other = (DataExample) o;
if (!other.canEqual((Object)this)) return false;
if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
if (this.getAge() != other.getAge()) return false;
if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
return true;
}
@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
final long temp1 = Double.doubleToLongBits(this.getScore());
result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode());
result = (result*PRIME) + this.getAge();
result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
result = (result*PRIME) + Arrays.deepHashCode(this.getTags());
return result;
}
2、@value
@Value 是 @Data 的不可變版本,用作不可變
@Value
public class ValueExample {
String name;
@Wither(AccessLevel.PACKAGE) @NonFinal int age;
double score;
protected String[] tags;
@ToString(includeFieldNames=true)
@Value(staticConstructor="of")
public static class Exercise<T> {
String name;
T value;
}
}
翻譯過後為:
public final class ValueExample {
private final String name;
private int age;
private final double score;
protected final String[] tags;
@java.beans.ConstructorProperties({"name", "age", "score", "tags"})
public ValueExample(String name, int age, double score, String[] tags) {
this.name = name;
this.age = age;
this.score = score;
this.tags = tags;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public double getScore() {
return this.score;
}
public String[] getTags() {
return this.tags;
}
@java.lang.Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof ValueExample)) return false;
final ValueExample other = (ValueExample)o;
final Object this$name = this.getName();
final Object other$name = other.getName();
if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
if (this.getAge() != other.getAge()) return false;
if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
return true;
}
@java.lang.Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $name = this.getName();
result = result * PRIME + ($name == null ? 43 : $name.hashCode());
result = result * PRIME + this.getAge();
final long $score = Double.doubleToLongBits(this.getScore());
result = result * PRIME + (int)($score >>> 32 ^ $score);
result = result * PRIME + Arrays.deepHashCode(this.getTags());
return result;
}
@java.lang.Override
public String toString() {
return "ValueExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")";
}
ValueExample withAge(int age) {
return this.age == age ? this : new ValueExample(name, age, score, tags);
}
public static final class Exercise<T> {
private final String name;
private final T value;
private Exercise(String name, T value) {
this.name = name;
this.value = value;
}
public static <T> Exercise<T> of(String name, T value) {
return new Exercise<T>(name, value);
}
public String getName() {
return this.name;
}
public T getValue() {
return this.value;
}
@java.lang.Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof ValueExample.Exercise)) return false;
final Exercise<?> other = (Exercise<?>)o;
final Object this$name = this.getName();
final Object other$name = other.getName();
if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
final Object this$value = this.getValue();
final Object other$value = other.getValue();
if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false;
return true;
}
@java.lang.Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $name = this.getName();
result = result * PRIME + ($name == null ? 43 : $name.hashCode());
final Object $value = this.getValue();
result = result * PRIME + ($value == null ? 43 : $value.hashCode());
return result;
}
@java.lang.Override
public String toString() {
return "ValueExample.Exercise(name=" + getName() + ", value=" + getValue() + ")";
}
}
}
3、@setter、 @Getter
註解在屬性上;為屬性提供 setting 、getting 方法
public class GetterSetterExample {
@Getter @Setter private int age = 10;
@Setter(AccessLevel.PROTECTED) private String name;
@Override public String toString() {
return String.format("%s (age: %d)", name, age);
}
}
翻譯後:
public class GetterSetterExample {
private int age = 10;
private String name;
@Override public String toString() {
return String.format("%s (age: %d)", name, age);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
protected void setName(String name) {
this.name = name;
}
}
4、@Log
註解在類上;為類提供一個 屬性為log 日誌物件
該註解,有以下幾種可以選擇:
@CommonsLog
Creates private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
@JBossLog
Creates private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);
@Log
Creates private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
@Log4j
Creates private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
@Log4j2
Creates private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
@Slf4j
Creates private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
@XSlf4j
Creates private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
5、@Getter(lazy = true)
使用了getter這個annotation可以在實際使用到cached的時候生成cached,同時,Lombok會自動去管理執行緒安全的問題,不會存在重複賦值的問題。如下程式碼所示
public class GetterLazyExample {
@Getter(lazy = true)
private final double[] cached = expensive();
public double[] expensive() {
double[] result = new double[10000000];
for (int i = 0; i < result.length; i++) {
result[i] = Math.abs(i);
}
return result;
}
}
6、@ToString
生成toString()方法,預設情況下,它會按順序(以逗號分隔)列印你的類名稱以及每個欄位。可以這樣設定不包含哪些欄位@ToString(exclude = "id") / @ToString(exclude = {"id","name"})
如果繼承的有父類的話,可以設定callSuper 讓其呼叫父類的toString()方法,例如:@ToString(callSuper = true)@ToString(exclude="id")
public class ToStringExample {
private static final int STATIC_VAR = 10;
private String name;
private Shape shape = new Square(5, 10);
private String[] tags;
private int id;
public String getName() {
return this.getName();
}
@ToString(callSuper=true, includeFieldNames=true)
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
}
}
翻譯後:public class ToStringExample {
private static final int STATIC_VAR = 10;
private String name;
private Shape shape = new Square(5, 10);
private String[] tags;
private int id;
public String getName() {
return this.getName();
}
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
@Override public String toString() {
return "Square(super=" + super.toString() + ", width=" + this.width + ", height=" + this.height + ")";
}
}
@Override public String toString() {
return "ToStringExample(" + this.getName() + ", " + this.shape + ", " + Arrays.deepToString(this.tags) + ")";
}
}
@ToString(exclude = {"id","name"})
public class User {
private Integer id;
private String name;
private String phone;
}
生成的toString方法為:
public String toString(){
return "User(phone=" + phone + ")";
}
7、@NoArgsConstructor、@AllArgsConstructor、@RequiredArgsConstructor
@NoArgsConstructor生成一個無參構造方法。當類中有final欄位沒有被初始化時,編譯器會報錯,此時可用@NoArgsConstructor(force = true),然後就會為沒有初始化的final欄位設定預設值 0 / false / null。對於具有約束的欄位(例如@NonNull欄位),不會生成檢查或分配,因此請注意,正確初始化這些欄位之前,這些約束無效。
@AllArgsConstructor:註解在類上;為類提供一個全參的構造方法
@RequiredArgsConstructor會生成構造方法(可能帶引數也可能不帶引數),如果帶引數,這引數只能是以final修飾的未經初始化的欄位,或者是以@NonNull註解的未經初始化的欄位
@RequiredArgsConstructor(staticName = "of")會生成一個of()的靜態方法,並把構造方法設定為私有的8、@Cleanup
public static void main(String[] args) throws IOException {
@Cleanup InputStream in = new FileInputStream(args[0]);
@Cleanup OutputStream out = new FileOutputStream(args[1]);
byte[] b = new byte[10000];
while (true) {
int r = in.read(b);
if (r == -1) break;
out.write(b, 0, r);
}
}
相當於:
public static void main(String[] args) throws IOException {
InputStream in = new FileInputStream(args[0]);
try {
OutputStream out = new FileOutputStream(args[1]);
try {
byte[] b = new byte[10000];
while (true) {
int r = in.read(b);
if (r == -1) break;
out.write(b, 0, r);
}
} finally {
if (out != null) {
out.close();
}
}
@Cleanup 存在一個小問題:官網給出了提示,如果你的程式碼中出現了異常,那麼會觸發cleanup方法丟擲異常,導致把原始異常吞掉,這樣就導致你在上層不知道發生了什麼事情,這個事情很嚴重啊,但是如果說你是在呼叫close方法的時候出了異常,那麼Cleanup這個annotation是不會把異常吞掉的。同時,官網也指出,作業也沒有找到更好的方式去解決這個問題,如果他們找到了會立刻fix這個問題,否則也就只能等待Java 有可能在Update的時候出現新的解決方案。
java7以後建議使用java自帶的try-with-resources語句
9、 @Builder
@Builder註解可以自動生成build()方法,構造一個例項屬性不需要單獨set
@Builder
@Data
public class BuilderExample {
private int age;
@Singular private Set<String> names;
public static void main(String[] args) throws IOException {
BuilderExample aBuilderExample = BuilderExample.builder().name("1").name("1").name("zs").build();
System.out.println(aBuilderExample);
}
}
上面的例子輸出結果為:
其中@Singular註解用於註釋集合欄位,該欄位的命名必須採用複數的形式,在使用build方法的時候,改欄位的單數形式可以直接當做非集合的方式進行賦值。
builder是現在比較推崇的一種構建值物件的方式。
可以看下官方示例,對比一下就都明白了
@Builder
public class BuilderExample {
private String name;
private int age;
@Singular private Set<String> occupations;
}
翻譯後為
class BuilderExample {
private String name;
private int age;
private Set<String> occupations;
BuilderExample(String name, int age, Set<String> occupations) {
this.name = name;
this.age = age;
this.occupations = occupations;
}
public static BuilderExampleBuilder builder() {
return new BuilderExampleBuilder();
}
public static class BuilderExampleBuilder {
private String name;
private int age;
private java.util.ArrayList<String> occupations;
BuilderExampleBuilder() {
}
public BuilderExampleBuilder name(String name) {
this.name = name;
return this;
}
public BuilderExampleBuilder age(int age) {
this.age = age;
return this;
}
public BuilderExampleBuilder occupation(String occupation) {
if (this.occupations == null) {
this.occupations = new java.util.ArrayList<String>();
}
this.occupations.add(occupation);
return this;
}
public BuilderExampleBuilder occupations(Collection<? extends String> occupations) {
if (this.occupations == null) {
this.occupations = new java.util.ArrayList<String>();
}
this.occupations.addAll(occupations);
return this;
}
public BuilderExampleBuilder clearOccupations() {
if (this.occupations != null) {
this.occupations.clear();
}
return this;
}
public BuilderExample build() {
// complicated switch statement to produce a compact properly sized immutable set omitted.
// go to https://projectlombok.org/features/Singular-snippet.html to see it.
Set<String> occupations = ...;
return new BuilderExample(name, age, occupations);
}
@java.lang.Override
public String toString() {
return "BuilderExample.BuilderExampleBuilder(name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")";
}
}
}
10、@NonNull
相當於自動加上是否為null的判斷
public class NonNullExample {
private String name;
public NonNullExample(@NonNull Person person) {
this.name = person.getName();
}
}
相當於:
public class NonNullExample{
private String name;
public NonNullExample(Person person) {
if (person == null) {
throw new NullPointerException("person");
}
this.name = person.getName();
}
}
Lombok原理
說道 Lombok,我們就得去提到 JSR 269: Pluggable Annotation Processing API (https://www.jcp.org/en/jsr/detail?id=269) 。JSR 269 之前我們也有註解這樣的神器,可是我們比如想要做什麼必須使用反射,反射的方法侷限性較大。首先,它必須定義@Retention為RetentionPolicy.RUNTIME,只能在執行時通過反射來獲取註解值,使得執行時程式碼效率降低。其次,如果想在編譯階段利用註解來進行一些檢查,對使用者的某些不合理程式碼給出錯誤報告,反射的使用方法就無能為力了。而 JSR 269 之後我們可以在 Javac的編譯期利用註解做這些事情。所以我們發現核心的區分是在 執行期 還是 編譯期。
從上圖可知,Annotation Processing 是在解析和生成之間的一個步驟。
上圖是 Lombok 處理流程,在Javac 解析成抽象語法樹之後(AST), Lombok 根據自己的註解處理器,動態的修改 AST,增加新的節點(所謂程式碼),最終通過分析和生成位元組碼。