一、介绍
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", "test@1ju.org", 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
检查)来增强它。
0 评论