Lombok Using @With Annotations – 使用@With注释的Lombok

最后修改: 2021年 12月 14日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

Lombok is a library that helps us significantly reduce boilerplate code when writing Java applications.

Lombok是一个库,可以帮助我们在编写Java应用程序时大幅减少模板代码。

In this tutorial, we’ll see how we can make copies of immutable objects with changes to only a single property using this library.

在本教程中,我们将看到如何使用这个库来制作只改变一个属性的不可变对象的副本。

2. Usage

2.使用情况

When working with immutable objects, which by design don’t allow setters, we may need a similar object to the current one, but with only one property different. This can be achieved using Lombok’s @With annotation:

当处理不可变的对象时,由于设计上不允许设置器,我们可能需要一个与当前对象相似的对象,但只有一个属性不同。这可以通过Lombok的@With注解来实现。

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

    //getters, constructors
}

The above annotation generates the following under the hood:

上述注解在引擎盖下生成了以下内容。

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);
    }
}

We can then use the above-generated method to create mutated copies of the original object:

然后我们可以使用上述生成的方法来创建原始对象的变异副本。

User immutableUser = new User("testuser", "test@mail.com", false);
User authenticatedUser = immutableUser.withAuthenticated(true);

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

Additionally, we have the option of annotating the whole class, which will generate withX() methods for all the properties.

此外,我们可以选择注释整个类,这将为所有属性生成withX()方法

3. Requirements

3.要求

To use the @With annotation correctly, we need to provide an all-arguments constructor. As we can see from the above example, the generated method requires this to create a clone of the original object.

为了正确使用@With注解,我们需要提供一个全参数构造函数。从上面的例子中我们可以看到,生成的方法需要这样来创建一个原始对象的克隆。

We can use either Lombok’s own @AllArgsConstructor or @Value annotation to satisfy this requirement. Alternatively, we can manually provide this constructor as well while ensuring that the order of the non-static properties in the class matches that of the constructor.

我们可以使用Lombok自己的@AllArgsConstructor@Value注解来满足这个要求。另外,我们也可以手动提供这个构造函数,同时确保类中的非静态属性的顺序与构造函数的顺序一致。

We should remember that the @With annotation does nothing if used on static fields. This is because static properties are not considered part of an object’s state. Also, Lombok skips the method generation for fields that start with the $ sign.

我们应该记住,@With注解如果用在静态字段上,则没有任何作用。这是因为静态属性不被认为是对象状态的一部分。另外,Lombok跳过了$符号开始的字段的方法生成

4. Advanced Usage

4.高级用法

Let’s investigate some advanced scenarios when using this annotation.

让我们研究一下使用这个注释时的一些高级场景。

4.1. Abstract Classes

4.1.抽象类

We can use the @With annotation on a field of an abstract class:

我们可以在一个抽象类的字段上使用@With注解。

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

    //getters, constructor
}

However, we will need to provide an implementation for the generated withInspected() method. This is because Lombok will have no idea about the concrete implementations of our abstract class to create clones of it:

然而,我们将需要为生成的withInspected()方法提供一个实现。这是因为Lombok将不知道我们的抽象类的具体实现来创建它的克隆。

public class KioskDevice extends Device {

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

    //getters, constructor
}

4.2. Naming Conventions

4.2.命名规则

As we identified above, Lombok will skip fields that start with the $ sign. However, if the field starts with a character, then it is title-cased, and finally, with is prefixed to the generated method.

正如我们上面所确定的,Lombok将跳过以$符号开始的字段。然而,如果字段以字符开头,那么它就会被冠以大写字母,最后,with被放在生成方法的前缀。

Alternatively, if the field starts with an underscore, then with is simply prefixed to the generated method:

另外,如果字段以下划线开头,那么with就会简单地作为生成方法的前缀。

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

    //getters, constructor excluding $variableC
}

According to the above code, we see that only the first two variables will have withX() methods generated for them:

根据上面的代码,我们看到只有前两个变量会有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. Exceptions to Method Generation

4.3.方法生成的例外情况

We should be mindful that in addition to fields that start with the $ sign, Lombok will not generate a withX() method if it already exists in our class:

我们应该注意,除了以$符号开头的字段,如果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
}

In the above scenario, no new withSku() method will be generated.

在上述情况下,没有新的withSku()方法将被生成。

Additionally, Lombok skips method generation in the following scenario:

此外,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
}

We can notice the different naming of the withSKU() method above.

我们可以注意到上面的withSKU()方法的不同命名。

Basically, Lombok will skip method generation if:

基本上,龙目岛会跳过方法生成,如果。

  • The same method exists as the generated method name (ignoring case)
  • The existing method has the same number of arguments as the generated method (including var-args)

4.4. Null Validations on Generated Methods

4.4.对生成的方法进行空值验证

Similar to other Lombok annotations, we can include null checks to the methods generated using the @With annotation:

与其他Lombok注解类似,我们可以在使用@With注解生成的方法中加入null检查。

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

Lombok will generate the following code for us along with the required null checks:

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. Conclusion

5.总结

In this article, we have seen how to use Lombok’s @With annotations to generate clones of a particular object with a change in a single field.

在这篇文章中,我们已经看到了如何使用Lombok的@With注解来生成某个特定对象的克隆,并在单个字段中进行更改。

We also learned how and when this method generation actually works, along with how to augment it with additional validations such as null checks.

我们还学习了这种方法生成的实际工作方式和时间,以及如何用额外的验证(如null检查)来增强它。

As always, the code examples are available over on GitHub.

像往常一样,代码示例可在GitHub上获得