Lombok使用@With註釋

一、介紹

Lombok 是一個庫,可幫助我們在編寫 Java 應用程序時顯著減少樣板代碼。

在本教程中,我們將看到如何使用此庫製作僅更改單個屬性的不可變對象的副本。

2. 用法

當使用不允許設置器的不可變對象時,我們可能需要一個與當前對像類似的對象,但只有一個屬性不同。這可以使用 Lombok 的@With註釋來實現:

public class User {
 private final String username;
 private final String emailAddress;
 @With
 private final boolean isAuthenticated;

 //getters, constructors
 }

上面的註釋在後台生成以下內容:

public class User {
 private final String username;
 private final String emailAddress;
 private final boolean isAuthenticated;

 //getters, constructors

 public User withAuthenticated(boolean isAuthenticated) {
 return this.isAuthenticated == isAuthenticated ? this : new User(this.username, this.emailAddress, isAuthenticated);
 }
 }

然後我們可以使用上面生成的方法來創建原始對象的變異副本:

User immutableUser = new User("testuser", "[email protected]", false);
 User authenticatedUser = immutableUser.withAuthenticated(true);

 assertNotSame(immutableUser, authenticatedUser);
 assertFalse(immutableUser.isAuthenticated());
 assertTrue(authenticatedUser.isAuthenticated());

此外,我們可以選擇註釋整個類,這將為所有屬性withX()方法

三、要求

@With註釋,我們需要提供一個全參數構造函數。從上面的例子我們可以看出,生成的方法需要 this 來創建原始對象的克隆。

我們可以使用 Lombok 自己的@AllArgsConstructor@Value註釋來滿足此要求。或者,我們也可以手動提供此構造函數,同時確保類中非靜態屬性的順序與構造函數的順序相匹配。

我們應該記住,如果在靜態字段上使用@With**註釋,則什麼也不做。這是因為靜態屬性不被視為對象狀態的一部分。此外,Lombok 會跳過$符號開頭的字段**的方法生成。

4. 高級用法

讓我們研究一下使用此註釋時的一些高級場景。

4.1.抽像類

我們可以在抽像類的字段上@With

public abstract class Device {
 private final String serial;
 @With
 private final boolean isInspected;

 //getters, constructor
 }

但是,我們需要為生成的withInspected()方法提供一個實現。這是因為 Lombok 不知道我們抽像類的具體實現來創建它的克隆:

public class KioskDevice extends Device {

 @Override
 public Device withInspected(boolean isInspected) {
 return new KioskDevice(getSerial(), isInspected);
 }

 //getters, constructor
 }

4.2.命名約定

如上所述,Lombok 將跳過以$符號開頭的字段。但是,如果字段以字符開頭,則它是標題大小寫的,最後, with是生成方法的前綴。

或者,如果該字段以下劃線開頭,則with只是作為生成方法的前綴:

public class Holder {
 @With
 private String variableA;
 @With
 private String _variableB;
 @With
 private String $variableC;

 //getters, constructor excluding $variableC
 }

根據上面的代碼,我們看到只有前兩個變量 將為它們生成withX()

Holder value = new Holder("a", "b");

 Holder valueModifiedA = value.withVariableA("mod-a");
 Holder valueModifiedB = value.with_variableB("mod-b");
 // Holder valueModifiedC = value.with$VariableC("mod-c"); not possible

4.3.方法生成的例外

我們應該注意,除了以$符號開頭的字段之外,如果我們的類中已經存在withX()方法,Lombok 將不會生成它:

public class Stock {
 @With
 private String sku;
 @With
 private int stockCount;

 //prevents another withSku() method from being generated
 public Stock withSku(String sku) {
 return new Stock("mod-" + sku, stockCount);
 }

 //constructor
 }

在上述場景中,不會生成新的withSku()方法。

此外,Lombok在以下場景中會****跳過方法生成:

public class Stock {
 @With
 private String sku;
 private int stockCount;

 //also prevents another withSku() method from being generated
 public Stock withSKU(String... sku) {
 return sku == null || sku.length == 0 ?
 new Stock("unknown", stockCount) :
 new Stock("mod-" + sku[0], stockCount);
 }

 //constructor
 }

我們可以注意到上面withSKU()

基本上,如果出現以下情況,Lombok 將跳過方法生成:

  • 與生成的方法名存在相同的方法(忽略大小寫)
  • 現有方法與生成的方法具有相同數量的參數(包括 var-args)

4.4.生成方法的空驗證

與其他 Lombok 註釋類似,我們可以@With註釋生成的方法進行null

@With
 @AllArgsConstructor
 public class ImprovedUser {
 @NonNull
 private final String username;
 @NonNull
 private final String emailAddress;
 }

Lombok 將為我們生成以下代碼以及所需的null檢查:

public ImprovedUser withUsername(@NonNull String username) {
 if (username == null) {
 throw new NullPointerException("username is marked non-null but is null");
 } else {
 return this.username == username ? this : new ImprovedUser(username, this.emailAddress);
 }
 }

 public ImprovedUser withEmailAddress(@NonNull String emailAddress) {
 if (emailAddress == null) {
 throw new NullPointerException("emailAddress is marked non-null but is null");
 } else {
 return this.emailAddress == emailAddress ? this : new ImprovedUser(this.username, emailAddress);
 }
 }

5. 結論

在本文中,我們已經看到瞭如何使用 Lombok 的@With註釋來生成具有單個字段更改的特定對象的克隆。

我們還了解了此方法生成的實際工作方式和時間,以及如何通過其他驗證(例如null檢查)來增強它。