目錄
和其他語言相比, Java經常因爲不必要的冗長被批評。 Lombok提供了一系列註解用以在後臺生成模板代碼,將其從你的類中刪除,從而有助於保持你的代碼整潔。較少的模板意味着更簡潔的代碼,更易於閱讀和維護。在本文中,我將涉及我經常使用的 Lombok功能,並想你展示如何使用他們生產更清晰、更簡潔的代碼。
1.局部變量類型推斷:val 和 var
許多語言通過查看等號右側的表達式來推斷局部變量類型。儘管現在 Java10+已經支持這種功能,但在之前的版本中沒有 Lombok的幫助就無法實現。下面的代碼段展示瞭如何顯式指定局部類型:
final Map<String, Integer> map = new HashMap<>();
map.put("Joe", 21);
在 Lombok中,我們可以通過使用 val來縮短它,如下所示:
val valMap = new HashMap<String, Integer>();
valMap.put("Sam", 30);
注意, val在背後創建了一個 final且不可變的變量。如果你需要一個可變本地變量,可以使用 var。
2.@NonNull
對方法參數進行 null檢查通常不是一個壞主意,特別是如果該方法形成的 API被其他開發者使用。雖然這些檢查很簡單,但是他們可能變得冗長,特別是當你有多個參數時。如下所示,額外的代碼無助於可讀性,並且可能從方法的主要目的分散注意力。
public void nonNullDemo(Employee employee, Account account) {
if(employee == null) {
throw new IllegalArgumentException("Employee is marked @NonNull but is null");
}
if(account == null) {
throw new IllegalArgumentException("Account is marked @NonNull but is null");
}
// do stuff
}
理想情況下,你需要 null檢查——沒有干擾的那種。這就是 @NonNull發揮作用的地方。通過用 @NonNull標記參數, Lombok替你爲該參數生成 null檢查。你的方法突然變得更加簡潔,但沒有丟失那些安全性的 null檢查。
public void nonNullDemo(@NonNull Employee employee, @NonNull Account account) {
// just do stuff
}
默認情況下, Lombok會拋出 NullPointerException,如果你願意,可以配置 Lombok拋出 IllegalArgumentException。我個人更喜歡 IllegalArgumentException,因爲我認爲它更適合於對參數檢查。
3.更簡潔的數據類
數據類是 Lombok真正有助於減少模板代碼的領域。在查看該選項前,思考一下我們經常需要處理的模板種類。數據類通常包括以下一種或全部:
構造函數(有或沒有參數)
私有成員變量的 getter 方法
私有非 final 成員變量的 setter 方法
幫助記錄日誌的 toString 方法
equals 和 hashCode(處理相等/集合)
可以通過 IDE 生成以上內容,因此問題不在於編寫他們花費的時間。問題是帶有少量成員變量的簡單類很快會變得非常冗長。讓我們看看 Lombok如何通過處理上述的每一項來減少混亂。
3.1. @Getter 和 @Setter
想想下面的 Car類。當生成 getter和 setter時,我們會得到接近 50 行代碼來描述一個包含 5 個成員變量的類。
public class Car {
private String make;
private String model;
private String bodyType;
private int yearOfManufacture;
private int cubicCapacity;
public String getMake() {
return make;
}
public void setMake(String make) {
this.make = make;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getBodyType() {
return bodyType;
}
public void setBodyType(String bodyType) {
this.bodyType = bodyType;
}
public int getYearOfManufacture() {
return yearOfManufacture;
}
public void setYearOfManufacture(int yearOfManufacture) {
this.yearOfManufacture = yearOfManufacture;
}
public int getCubicCapacity() {
return cubicCapacity;
}
public void setCubicCapacity(int cubicCapacity) {
this.cubicCapacity = cubicCapacity;
}
}
Lombok可以替你生成 getter和 setter模板。通過對每個成員變量使用 @Getter和 @Setter註解,你最終得到一個等效的類,如下所示:
-
public class Car { @Getter @Setter private String make; @Getter @Setter private String model; @Getter @Setter private String bodyType; @Getter @Setter private int yearOfManufacture; @Getter @Setter private int cubicCapacity; }
注意,你可以在非 final成員變量上只使用 @Setter。在 final成員變量上使用它將導致編譯錯誤。
如果你需要爲每個成員變量生成 getter和 setter,你也可以在類級別使用 @Getter和 @Setter,如下所示。
@Getter
@Setter
public class Car {
private String make;
private String model;
private String bodyType;
private int yearOfManufacture;
private int cubicCapacity;
}
3.2. @AllArgsConstructor
數據類通常包含一個構造函數,它爲每個成員變量接受參數。IDE 爲 Car生成的構造函數如下所示:
public class Car {
@Getter @Setter
private String make;
@Getter @Setter
private String model;
@Getter @Setter
private String bodyType;
@Getter @Setter
private int yearOfManufacture;
@Getter @Setter
private int cubicCapacity;
public Car(String make, String model, String bodyType, int yearOfManufacture, int cubicCapacity) {
super();
this.make = make;
this.model = model;
this.bodyType = bodyType;
this.yearOfManufacture = yearOfManufacture;
this.cubicCapacity = cubicCapacity;
}
}
我們可以使用 @AllArgsConstructor註解實現同樣功能。 @Getter和 @Setter、 @AllArgsConstructor減少模板,保持類更乾淨且更簡潔。
@AllArgsConstructor
public class Car {
@Getter @Setter
private String make;
@Getter @Setter
private String model;
@Getter @Setter
private String bodyType;
@Getter @Setter
private int yearOfManufacture;
@Getter @Setter
private int cubicCapacity;
}
還有其他選項用於生成構造函數。 @RequiredArgsConstructor將創建帶有每個 final成員變量參數的構造函數, @NoArgsConstructor將創建沒有參數的構造函數。
3.3. @ToString
在你的數據類上覆蓋 toString方法是有助於記錄日誌的良好實踐。IDE 爲 Car類生成的 toString方法如下所示:
@AllArgsConstructor
public class Car {
@Getter @Setter
private String make;
@Getter @Setter
private String model;
@Getter @Setter
private String bodyType;
@Getter @Setter
private int yearOfManufacture;
@Getter @Setter
private int cubicCapacity;
@Override
public String toString() {
return "Car [make=" + make + ", model=" + model + ", bodyType=" + bodyType + ", yearOfManufacture="
+ yearOfManufacture + ", cubicCapacity=" + cubicCapacity + "]";
}
}
我們可以使用 ToString註解廢除這個,如下所示:
@ToString
@AllArgsConstructor
public class Car {
@Getter @Setter
private String make;
@Getter @Setter
private String model;
@Getter @Setter
private String bodyType;
@Getter @Setter
private int yearOfManufacture;
@Getter @Setter
private int cubicCapacity;
}
默認情況下, Lombok生成包含所有成員變量的 toString方法。可以通過 exclude屬性 @ToString(exclude={"someField"},"someOtherField"}) 覆蓋行爲將某些成員變量排除。
3.4. @EqualsAndHashCode
如果你正在將你的數據類和任何類型的對象比較,則需要覆蓋 equals和 hashCode 方法。對象的相等是基於業務規則定義的。舉個例子,在 Car類中,如果兩個對象有相同的 make、 model和 bodyType,我可能認爲他們是相等的。如果我使用 IDE 生成 equals方法檢查 make、 model和 bodyType,它看起來會是這樣:
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Car other = (Car) obj;
if (bodyType == null) {
if (other.bodyType != null)
return false;
} else if (!bodyType.equals(other.bodyType))
return false;
if (make == null) {
if (other.make != null)
return false;
} else if (!make.equals(other.make))
return false;
if (model == null) {
if (other.model != null)
return false;
} else if (!model.equals(other.model))
return false;
return true;
}
等價的 hashCode實現如下所示:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((bodyType == null) ? 0 : bodyType.hashCode());
result = prime * result + ((make == null) ? 0 : make.hashCode());
result = prime * result + ((model == null) ? 0 : model.hashCode());
return result;
}
雖然 IDE 處理了繁重的工作,但我們在類中仍然有大量的模板代碼。 Lombok允許我們使用 @EqualsAndHashCode類註解實現相同的功能,如下所示:
@ToString
@AllArgsConstructor
@EqualsAndHashCode(exclude = { "yearOfManufacture", "cubicCapacity" })
public class Car {
@Getter @Setter
private String make;
@Getter @Setter
private String model;
@Getter @Setter
private String bodyType;
@Getter @Setter
private int yearOfManufacture;
@Getter @Setter
private int cubicCapacity;
}
默認情況下, @EqualsAndHashCode會創建包含所有成員變量的 equals和 hashCode方法。 exclude選項可用於通知 Lombok排除某些成員變量。在上面的代碼片段中。我已經從生成的 equals和 hashCode方法中排除了 yearOfManuFacture 和 cubicCapacity。
3.5. @Data
如果你想使數據類儘可能精簡,可以使用 @Data註解。 @Data 是 @Getter、 @Setter、 @ToString、 @EqualsAndHashCode 和 @RequiredArgsConstructor 的快捷方式。
@ToString
@RequiredArgsConstructor
@EqualsAndHashCode(exclude = { "yearOfManufacture", "cubicCapacity" })
public class Car {
@Getter @Setter
private String make;
@Getter @Setter
private String model;
@Getter @Setter
private String bodyType;
@Getter @Setter
private int yearOfManufacture;
@Getter @Setter
private int cubicCapacity;
}
通過使用 @Data,我們可以將上面的類精簡如下:
@Data
public class Car {
private String make;
private String model;
private String bodyType;
private int yearOfManufacture;
private int cubicCapacity;
}
4. 使用 @Buidler 創建對象
建造者設計模式描述了一種靈活的創建對象的方式。 Lombok可以幫你輕鬆的實現該模式。看一個使用簡單 Car類的示例。假設我們希望可以創建各種 Car對象,但我們希望在創建時設置的屬性具有靈活性。
@AllArgsConstructor
public class Car {
private String make;
private String model;
private String bodyType;
private int yearOfManufacture;
private int cubicCapacity;
private List<LocalDate> serviceDate;
}
假設我們要創建一個 Car,但只想設置 make和 model。在 Car上使用標準的全參數構造函數意味着我們只提供 make和 model並設置其他參數爲 null。
Car2 car2 = new Car2("Ford", "Mustang", null, null, null, null);
這可行但並不理想,我們必須爲我們不感興趣的參數傳遞 null。我們可以創建一個只接受 make和 model的構造函數來避開這個問題。這是一個合理的解決方法,但不夠靈活。如果我們有許多不同的字段排列,我們可以用什麼來創建一個新 Car?最終我們得到了一堆不同的構造函數,代表了我們可以實例化 Car的所有可能方式。
解決該問題的一種乾淨、靈活的方式是使用建造者模式。 Lombok通過 @Builder 註解幫你實現建造者模式。當你使用 @Builder註解 Car類時, Lombok會執行以下操作:
- 添加一個私有構造函數到 Car
- 創建一個靜態的 CarBuilder類
- 在 CarBuilder中爲 Car中的每個成員創建一個 setter風格方法。
- 在 CarBuilder中添加創建 Car的新實例的建造方法。
CarBuilder上的每個 setter風格方法返回自身的實例( CarBuilder)。這允許你進行方法鏈式調用併爲對象創建提供流暢的 API。讓我們看看它如何使用。
Car muscleCar = Car.builder().make("Ford")
.model("mustang")
.bodyType("coupe")
.build();
現在只使用 make和 model創建 Car比之前更簡潔了。只需在 Car上簡單的調用生成的 builder方法獲取 CarBuilder實例,然後調用任何我們感興趣的 setter風格方法。最後,調用 build創建 Car的新實例。
另一個值得一提的方便的註解是 @Singular。默認情況下,Lombok 爲集合創建使用集合參數的標準的 setter 風格方法。在下面的例子中,我們創建了新的 Car並設置了服務日期列表。
Car muscleCar = Car.builder().make("Ford")
.model("mustang")
.serviceDate(Arrays.asList(LocalDate.of(2016, 5, 4)))
.build();
向集合成員變量添加 @Singular將提供一個額外的方法,允許你向集合添加單個項。
@Builder
public class Car {
private String make;
private String model;
private String bodyType;
private int yearOfManufacture;
private int cubicCapacity;
@Singular
private List<LocalDate> serviceDate;
}
現在我們可以添加單個服務日期,如下所示:
Car muscleCar3 = Car.builder()
.make("Ford")
.model("mustang")
.serviceDate(LocalDate.of(2016, 5, 4))
.build();
這是一個有助於在創建對象期間處理集合時保持代碼簡潔的快捷方法。
5.日誌
Lombok另一個偉大的功能是日誌記錄器。如果沒有 Lombok,要實例化標準的 SLF4J日誌記錄器,通常會有以下內容:
public class SomeService {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class)
public void doStuff(){
log.debug("doing stuff....");
}
}
這些日誌記錄器很沉重,併爲每個需要日誌記錄的類添加了不必要的混亂。值得慶幸的是 Lombok提供了一個爲你創建日誌記錄器的註解。你要做的所有事情就是在類上添加註解,這樣就可以了。
@Slf4j
public class SomeService {
public void doStuff(){
log.debug("doing stuff....");
}
}
我在這裏使用了 @SLF4J註解,但 Lombok能爲幾乎所有通用 Java日誌框架生成日誌記錄器。有關更多日誌記錄器的選項,請參閱文檔。
6.Lombok給你控制權
我非常喜歡 Lombok的一點是它的不侵入性。。如果你決定在使用如 @Getter、 @Setter 或 @ToString時也想要自己的方法實現,你的方法將總是優先於 Lombok。它允許你在大多數時間使用 Lombok,但在你需要的時候仍有控制權。
7.寫得更少,做得更多
在過去的 4 到 5 年裏,我幾乎在每個項目中都使用了 Lombok。我喜歡它,因爲它減少了雜亂,最終得到了更乾淨、更簡潔、更易閱讀的代碼。它不一定爲你節省大量時間,因爲它生成的代碼可以由 IDE 自動生成。話雖如此,我認爲更乾淨的代碼的好處不僅僅是將其添加到 Java堆棧中。
8. 延展閱讀
我已經介紹了我經常使用的 Lombok功能,但還有很多我沒有講到。如果你喜歡目前爲止所看到的,並希望瞭解更多,請繼續閱讀 Lombok 文檔。
文章轉載自公衆號 鍋外的大佬 , 作者 Darren Luo
原文鏈接:https://dzone.com/articles/introduction-to-lombok
作者:Brian Hannaway
譯者:Darren Luo