Using the @Singular Annotation with Lombok Builders – 在Lombok Builders中使用@Singular注释

最后修改: 2019年 3月 9日

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

1. Overview

1.概述

The Lombok library provides a great way of simplifying data objects. One of the key features of Project Lombok is the @Builder annotation, which automatically creates Builder classes for creating immutable objects. However, populating collections in our objects can be clumsy with the standard Lombok-generated Builder classes.

Lombok库提供了一种简化数据对象的好方法。Project Lombok的主要功能之一是@Builder注解,它自动创建用于创建不可变对象的Builder类。然而,用标准的Lombok生成的Builder类在我们的对象中填充集合可能会很笨拙。

In this tutorial, we’re going to look at the @Singular annotation, which helps us to work with collections in our data objects. It also enforces good practices, as we’ll see.

在本教程中,我们将查看@Singular注解,它帮助我们在数据对象中使用集合。它还强制执行良好的实践,我们将看到。

2. Builders and Collections

2.建设者和收藏家

Builder classes make it easy to construct immutable data objects with their simple, fluent syntax. Let’s look at an example classes annotated with Lombok’s @Builder annotation:

Builder类以其简单、流畅的语法使构建不可变的数据对象变得容易。让我们看看一个用Lombok的@Builder注解的例子类。

@Getter
@Builder
public class Person {
    private final String givenName;
    private final String additionalName;
    private final String familyName;
    private final List<String> tags;
}

We can now create instances of Person using the builder pattern. Note here that the tags property is a List. Furthermore, the standard Lombok @Builder will provide methods to set this property just like for the non-list properties:

我们现在可以使用构建器模式创建Person的实例。注意这里的tags属性是一个List。此外,标准的Lombok @Builder将提供方法来设置这个属性,就像对非列表属性那样。

Person person = Person.builder()
  .givenName("Aaron")
  .additionalName("A")
  .familyName("Aardvark")
  .tags(Arrays.asList("fictional","incidental"))
  .build();

This is a workable but rather clumsy syntax. We can create the collection inline, as we’ve done above. Or, we can declare it ahead of time. Either way, it breaks the flow of our object creation. This is where the @Singular annotation comes in handy.

这是一个可行的但相当笨拙的语法。我们可以内联创建集合,就像我们上面做的那样。或者,我们可以提前声明它。无论哪种方式,它都会破坏我们创建对象的流程。这就是@Singular注解的用武之地。

2.1. Using the @Singular Annotation with Lists

2.1.使用@Singular注解与Lists

Let’s add another List to our Person object and annotate it with @Singular. This will give us a side-by-side view of one field that is annotated and one that isn’t. As well as the general tags property, we’ll add a list of interests to our Person:

让我们为我们的Person对象添加另一个List,并用@Singular来注释它。这将给我们一个被注释的字段和未被注释的字段的并列视图。除了一般的tags属性外,我们还将为我们的Person添加一个interests列表。

@Singular private final List<String> interests;

We can now build up a list of values one at a time:

我们现在可以一次建立一个价值列表:

Person person = Person.builder()
  .givenName("Aaron")
  .additionalName("A")
  .familyName("Aardvark")
  .interest("history")
  .interest("sport")
  .build();

The builder will store each element internally in a List and create the appropriate Collection when we invoke build().

构建器将在内部将每个元素存储在List中,并在我们调用build()时创建相应的Collection

2.2. Working with Other Collection Types

2.2.与其他集合类型一起工作

We’ve illustrated @Singular working with a java.util.List here, but it can also be applied to other Java Collection classes. Let’s add some more members to our Person:

我们在这里说明了@Singularjava.util.List的工作,但是它也可以应用于其他Java Collection。让我们为我们的Person增加一些成员。

@Singular private final Set<String> skills;
@Singular private final Map<String, LocalDate> awards;

A Set will behave much as a List, as far as Builders are concerned – we can add elements one by one:

Builders而言,Set的行为与List差不多,我们可以一个一个地添加元素。

Person person = Person.builder()
  .givenName("Aaron")
  .skill("singing")
  .skill("dancing")
  .build();

Because Set doesn’t support duplicates, we need to be aware that adding the same element multiple times won’t create multiple elements. The Builder will handle this situation leniently. We can add an element multiple times, but the created Set will have only one occurrence of the element.

因为Set不支持重复,我们需要注意的是,多次添加相同的元素并不会创建多个元素。Builder将宽松地处理这种情况。我们可以多次添加一个元素,但是创建的Set将只有一个元素的出现。

Maps are treated slightly differently, with the Builder exposing methods that take a key and value of the appropriate types:

Maps的处理方式略有不同,Builder暴露了接受适当类型的键和值的方法。

Person person = Person.builder()
  .givenName("Aaron")
  .award("Singer of the Year", LocalDate.now().minusYears(5))
  .award("Best Dancer", LocalDate.now().minusYears(2))
  .build();

As we saw with Sets, the builder is lenient with duplicate Map keys, and will use the last value if the same key is assigned more than once.

正如我们在Sets中看到的,构建器对重复的Map键很宽容,如果同一键被分配多次,将使用最后的值。

3. Naming of @Singular Methods

3.@Singular方法的命名

So far, we’ve relied on one bit of magic in the @Singular annotation without drawing attention to it. The Builder itself provides a method for assigning the entire collection at once that uses the plural form – “awards“, for example. The extra methods added by the @Singular annotation use the singular form – for example, “award“.

到目前为止,我们一直依赖于@Singular注解中的一点魔法,而没有引起注意。Builder本身提供了一个方法,用于一次性分配整个集合,该方法使用复数形式–例如,”awards“。@Singular注解添加的额外方法使用单数形式 – 例如,”award“。

Lombok is smart enough to recognize simple plural words, in English, where they follow a regular pattern. In all the examples we’ve used so far, it just removes the last ‘s’.

Lombok足够聪明,能够识别简单的复数词,在英语中,它们遵循一个常规模式。在我们目前使用的所有例子中,它只是删除了最后一个’s’。

It will also know that, for some words ending in “es”, to remove the last two letters. It knows, for example, that “grass” is the singular of “grasses”, and that “grape”, and not “grap”, is the singular of “grapes”. In some cases, though, we have to give it some help.

它还知道,对于一些以 “es “结尾的词,要去掉最后两个字母。例如,它知道 “草 “是 “grasses “的单数,”葡萄 “而不是 “grap “是 “grapes “的单数。不过,在某些情况下,我们必须给它一些帮助。

Let’s build a simple model of a sea, containing fish and sea-grasses:

让我们建立一个简单的海的模型,包含鱼和海草。

@Getter
@Builder
public class Sea {
    @Singular private final List<String> grasses;
    @Singular private final List<String> fish;
}

Lombok can handle the word “grasses”, but is lost with “fish”. In English, the singular and plural forms are the same, strangely enough. This code won’t compile, and we’ll get an error:

龙目岛可以处理 “草 “这个词,但在处理 “鱼 “这个词的时候就失去了意义。在英语中,单数和复数形式是一样的,很奇怪。这段代码不会被编译,我们会得到一个错误。

Can't singularize this name; please specify the singular explicitly (i.e. @Singular("sheep"))

We can sort things out by adding a value to the annotation to use as the singular method name:

我们可以通过在注解中添加一个值来作为单数方法的名称来整理事情。

@Singular("oneFish") private final List<String> fish;

We can now compile our code and use the Builder:

我们现在可以编译我们的代码并使用Builder

Sea sea = Sea.builder()
  .grass("Dulse")
  .grass("Kelp")
  .oneFish("Cod")
  .oneFish("Mackerel")
  .build();

In this case, we chose the rather contrived oneFish(), but the same method can be used with non-standard words that do have a distinct plural. For example, a List of children could be provided with a method child().

在这种情况下,我们选择了相当复杂的oneFish(),但是同样的方法也可以用于那些确实有明显复数的非标准词。例如,一个Listchildren可以用一个方法child()来提供。

4. Immutability

4.不变性

We’ve seen how the @Singular annotation helps us to work with collections in Lombok. Besides providing convenience and expressiveness, it can also help us to keep our code clean.

我们已经看到了@Singular注解如何帮助我们在Lombok中处理集合。除了提供便利性和表达性,它还可以帮助我们保持代码的干净。

Immutable objects are defined as objects that cannot be modified once they are created. Immutability is important in reactive architectures, for example, because it allows us to pass an object into a method with a guarantee of no side effects. The Builder pattern is most commonly used as an alternative to POJO getters and setters in order to support immutability.

不可变的对象被定义为一旦被创建就不能被修改的对象。不变性在反应式架构中非常重要,例如,它允许我们将一个对象传递到一个方法中,并保证不产生副作用。生成器模式最常被用作POJO获取器和设置器的替代品,以支持不可变性。

When our data objects contain Collection classes, it can be easy to let immutability slip a little. The base collection interfaces — List, Set, and Map — all have mutable and immutable implementations. If we rely on the standard Lombok builder, we can accidentally pass in a mutable collection, and then modify it:

当我们的数据对象包含Collection类时,我们很容易让不可变性稍有疏漏。基本集合接口–ListSetMap–都有可变和不可变的实现。如果我们依赖标准的Lombok构建器,我们可以不小心传入一个可变的集合,然后修改它。

List<String> tags= new ArrayList();
tags.add("fictional");
tags.add("incidental");
Person person = Person.builder()
  .givenName("Aaron")
  .tags(tags)
  .build();
person.getTags().clear();
person.getTags().add("non-fictional");
person.getTags().add("important");

We’ve had to work quite hard in this simple example to make the mistake. If we’d used Arrays.asList(), for example, to construct the variable tags, we would’ve gotten an immutable list for free, and calls to add() or clear() would throw an UnsupportedOperationException.

在这个简单的例子中,我们不得不努力地去犯这个错误。例如,如果我们使用Arrays.asList()来构造变量tags,我们将免费得到一个不可变的列表,而调用add()clear()将抛出UnsupportedOperationException

In real coding, the error is more likely to occur if the collection is passed in as a parameter, for example. However, it’s good to know that with @Singular, we can work with the base Collection interfaces and get immutable instances when we call build().

在实际编码中,如果集合被作为参数传入,例如,该错误更有可能发生。然而,使用@Singular,我们可以使用基本的Collection接口,并在调用build()时获得不可变的实例,这是好事。

5. Conclusion

5.总结

In this tutorial, we’ve seen how the Lombok @Singular annotation provides a convenient way of working with the List, Set, and Map interfaces using the Builder pattern. The Builder pattern supports immutability, and @Singular provides us with first-class support for this.

在本教程中,我们已经看到了Lombok @Singular 注解如何提供了一种方便的方式来使用ListSetMap接口的Builder模式。生成器模式支持不变性,而@Singular为我们提供了一流的支持。

As usual, the complete code examples are available over on GitHub.

像往常一样,完整的代码示例可在GitHub上找到