Перейти к основному содержимому

Использование аннотации @Singular с Lombok Builders

· 6 мин. чтения

1. Обзор

Библиотека Lombok предоставляет отличный способ упростить объекты данных. Одной из ключевых особенностей Project Lombok является аннотация @Builder , которая автоматически создает классы Builder для создания неизменяемых объектов. Однако заполнение коллекций в наших объектах может быть неуклюжим со стандартными классами Builder , сгенерированными Lombok.

В этом руководстве мы рассмотрим аннотацию @Singular , которая помогает нам работать с коллекциями в наших объектах данных. Как мы увидим, он также обеспечивает соблюдение передовой практики.

2. Строители и коллекции

Классы- строители упрощают создание неизменяемых объектов данных благодаря их простому и понятному синтаксису. Давайте посмотрим на пример классов, аннотированных аннотацией Lombok @Builder :

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

Теперь мы можем создавать экземпляры Person , используя шаблон построителя. Обратите внимание, что свойство tags — это List . Кроме того, стандартный Lombok @Builder предоставит методы для установки этого свойства, как и для свойств, не входящих в список:

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

Это работоспособный, но довольно неуклюжий синтаксис. Мы можем создать встроенную коллекцию, как мы сделали выше. Или мы можем объявить об этом заранее. В любом случае, это прерывает процесс создания нашего объекта. Здесь пригодится аннотация @Singular .

2.1. Использование аннотации @Singular со списками

Давайте добавим еще один список к нашему объекту Person и аннотируем его с помощью @Singular . Это даст нам параллельное представление одного поля, которое аннотировано, и другого, которое не аннотировано. Помимо общего свойства tags , мы добавим список интересов к нашему Person :

@Singular private final List<String> interests;

Теперь мы можем создать список значений по одному:

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

Построитель будет хранить каждый элемент внутри списка и создавать соответствующую коллекцию , когда мы вызываем build() .

2.2. Работа с другими типами коллекций

Здесь мы проиллюстрировали работу @Singular с java.util.List , но его также можно применять к другим классам Java Collection . Давайте добавим еще несколько членов к нашему Person :

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

Set будет вести себя так же, как List , что касается Builder s — мы можем добавлять элементы один за другим:

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

Поскольку Set не поддерживает дубликаты, мы должны знать, что многократное добавление одного и того же элемента не приведет к созданию нескольких элементов. Строитель снисходительно отнесется к этой ситуации . Мы можем добавить элемент несколько раз, но созданный набор будет иметь только одно вхождение элемента.

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

Как мы видели в случае с Sets , построитель снисходительно относится к дублирующимся ключам Map и будет использовать последнее значение, если один и тот же ключ назначается более одного раза.

3. Именование @Singular методов

До сих пор мы полагались на одну магию в аннотации @Singular, не привлекая к ней внимания. Сам Builder предоставляет метод присвоения всей коллекции сразу, который использует форму множественного числа — « награды », например. Дополнительные методы, добавленные аннотацией @Singular, используют форму единственного числа — например, « награда ».

Ломбок достаточно умен, чтобы распознавать простые слова во множественном числе в английском языке, где они следуют регулярному образцу. Во всех примерах, которые мы использовали до сих пор, он просто удаляет последнюю букву «s».

Он также будет знать, что для некоторых слов, оканчивающихся на «es», нужно удалить две последние буквы. Он знает, например, что «трава» есть единственное число от «травы», и что «виноград», а не «грапс», есть единственное число от «виноград». В некоторых случаях, однако, мы должны оказать ему некоторую помощь.

Давайте построим простую модель моря, содержащую рыбу и водоросли:

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

Ломбок может справиться со словом «травы», но теряется со словом «рыба». В английском языке формы единственного и множественного числа совпадают, как ни странно. Этот код не скомпилируется, и мы получим ошибку:

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

Мы можем разобраться, добавив в аннотацию значение, которое будет использоваться в качестве единственного имени метода:

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

Теперь мы можем скомпилировать наш код и использовать Builder :

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

В данном случае мы выбрали довольно надуманный метод oneFish() , но тот же метод можно использовать с нестандартными словами, которые имеют явное множественное число. Например, список дочерних элементов может быть предоставлен с помощью метода child() .

4. Неизменность

Мы видели, как аннотация @Singular помогает нам работать с коллекциями в Ломбоке. Помимо обеспечения удобства и выразительности, это также может помочь нам поддерживать чистоту нашего кода.

Неизменяемые объекты определяются как объекты, которые нельзя изменить после их создания. Неизменяемость важна, например, в реактивных архитектурах, потому что она позволяет нам передавать объект в метод с гарантией отсутствия побочных эффектов. Шаблон Builder чаще всего используется в качестве альтернативы геттерам и сеттерам POJO для поддержки неизменности.

Когда наши объекты данных содержат классы Collection , легко упустить неизменяемость. Базовые интерфейсы коллекций — List , Set и Map — имеют изменяемые и неизменяемые реализации. Если мы полагаемся на стандартный компоновщик 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");

В этом простом примере нам пришлось немало потрудиться, чтобы допустить ошибку. Если бы мы использовали Arrays.asList() , например, для создания тегов переменных , мы получили бы неизменяемый список бесплатно, а вызовы add() или clear() вызвали бы исключение UnsupportedOperationException .

В реальном кодировании ошибка может возникнуть, например, если коллекция передается в качестве параметра. Однако полезно знать, что с @Singular мы можем работать с базовыми интерфейсами Collection и получать неизменяемые экземпляры при вызове build() .

5. Вывод

В этом руководстве мы увидели, как аннотация Lombok @Singular обеспечивает удобный способ работы с интерфейсами List , Set и Map с использованием шаблона Builder. Шаблон Builder поддерживает неизменяемость, и @Singular предоставляет нам для этого первоклассную поддержку.

Как обычно, полные примеры кода доступны на GitHub .