Lombok @Builder

Цикл статей «Project Lombok».

Следующая статья — «Lombok @SneakyThrows — храбро бросаем проверяемые исключения там, где их ещё никто не бросал».
Предыдущая статья — «Lombok @Value — лёгкое создание неизменяемых классов».

Аннотация @Builder была представлена как новая возможность в Lombok v0.12.0.

Аннотация @Builder получила поддержку @Singular и была переведена в основной пакет Lombok в Lombok v1.16.0.

Аннотация @Builder создаёт составной builder API для ваших классов.

@Builder даёт вам возможность автоматически генерировать код, необходимый для создания экземпляров ваших классов кодом вида:

@Builder может быть указан для класса или конструктора или статического метода. Чаще всего он указывается для класса или для конструктора, но легче его объяснить на примере статического метода.

Статический метод с аннотацией @Builder (дальше я буду называть его целью) приводит к генерации семи вещей:

  • Статический вложенный класс с именем FooBuilder и с теми же аргументами, что и статический метод (называемый builder).
  • В builder: Одно приватное нестатическое поле для каждого параметра в цели.
  • В builder: Пустой конструктор без аргументов с модификатором доступа package private.
  • В builder: Метод для установки значения для каждого параметра. Он имеет тот же тип аргумента, что и параметр и то же имя. Он возвращает builder, так что методы установки значения могут быть выстроены в цепочку, как в примере выше.
  • В builder: Метод build(), вызывающий статический метод, передающий туда все поля. Он возвращает тот же тип, что и цель.
  • В builder: Подходящая реализация toString().
  • В классе, содержащем цель: Статический метод builder(), который создаёт экземпляр builder-а.

Любой из перечисленных элементов для генерации просто пропускается, если элемент уже существует (не зависимо от количество параметров, смотрится только имя). Это касается и самого builder — если класс уже существует, то Lombok просто вставляет поля и методы в уже существующий класс, если этих полей/методов ещё нет, конечно. Поэтому вы не можете добавлять любую другую аннотацию Lombok, генерирующую метод или конструктор к builder-у. Например, вы не можете добавить @EqualsAndHashCode к классу builder-а.

@Builder может генерировать так называемые singular-методы. Они берут один параметр вместо целого списка и добавляют элемент к списку. Например: Person.builder().job("Mythbusters").job("Unchained Reaction").build(); приведёт к тому, что List<String> jobs  поле будет иметь две строки. Для этого к полю/параметру должна быть добавлена аннотация @Singular.

Теперь когда со статическим методом всё понятно, указание аннотации @Builder для конструктора работает так же. Фактически конструкторы — это просто статические методы, имеющие особый синтаксис для их вызова. Их возвращаемый тип — это класс, который они создают, а их параметры те же, что и параметры класса.

В конечном итоге применение @Builder к классу — это то же самое, что и добавление @AllArgsConstructor(access = AccessLevel.PACKAGE) к классу и применение аннотации @Builder к этому all-args-конструктору. Это работает, только если вы не писали явных конструкторов. Если уже есть явный конструктор, то добавляйте аннотацию @Builder к этому конструктору, а не к классу.

Если вы используете @Builder для генерации builder-ов, создающих экземпляры вашего класса (это обычно так и есть, если вы не используете @Builder для статического метода, который не возвращает ваш тип), вы можете использовать @Builder(toBuilder = true)  для генерации метода экземпляров вашего класса toBuilder(). Он создаёт новый builder, который уже содержит все значения из текущего экземпляра. Вы можете добавить @Builder.ObtainVia к параметрам (в случае использования конструктора или статического метода) или полям (в случае @Builder для типа), для указания альтернативного способа, которым значения этих полей/параметров получаются из экземпляра. Например, вы можете указать выполнение метода: @Builder.ObtainVia(method = "calculateFoo") .

Имя класса builder-а — FoobarBuilder, где Foobar — это упрощённая, начинающаяся с заглавной буквы форма цели, то есть это имя вашего класса при использовании @Builder для конструкторов или типов, и это имя возвращаемого типа при использовании для статических методов.Например, если аннотация @Builder применена для класса с именем com.yoyodyne.FancyList, то имя builder-а будет FancyListBuilder. Если @Builder применён к статическому методу, возвращающему void, то класс будет с именем VoidBuilder.

Настраиваемые части builder-а:

  • Имя класса builder-а (по умолчанию: return type + 'Builder' )
  • Имя метода build() (по умолчанию: "build" )
  • Имя метода builder() (по умолчанию: "builder" )
  • Нужен ли toBuilder() (по умолчанию: нет)

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

@Singular

Если один из параметров (если аннотация @Builder была добавлена к статическому методу или конструктору) или полей (если аннотация @builder была добавлена к классу) помечен аннотацией @Singular, то Lombok считает, что этот элемент builder-а является коллекцией, и генерирует 2 метода добавления, вместо метода установки. Один метод добавляет один элемент в коллекцию, а другой метод добавляет все элементы из другой коллекции в эту коллекцию. Метод установки значения (заменяющий уже добавленные значения) не генерируется. Эти singular builder-ы так усложнены, для того чтобы гарантировать следующий свойства:

  • При вызове build(), возвращаемая коллекция будет неизменяемой.
  • Вызов одного из методов добавления после вызова build() не изменяет никакой из уже сгенерированных объектов, и если build() позже вызван ещё раз, то другая коллекция со всеми добавленными элементами с момента создания builder-а генерируется.
  • Созданная коллекция будет упакована в наименьший из возможных размеров, оставаясь эффективной.

Аннотация @Singular может быть применена только к типам коллекций, известных Lombok. На текущий момент поддерживаются:
java.util:

  • Iterable, Collection, и List (реализованные через упакованный неизменяемый ArrayList в обычном случае).
  • Set, SortedSet, и NavigableSet (реализованные через неизменяемый HashSet или TreeSet с разумным размером в обычном случае).
  • Map, SortedMap, и NavigableMap (реализованные через неизменяемый HashMap или TreeMap с разумным размером в обычном случае).

Guava com.google.common.collect:

  • ImmutableCollection и ImmutableList (реализованные через builder ImmutableList-а).
  • ImmutableSet и ImmutableSortedSet (реализованные через builder-ы этих типов).
  • ImmutableMap, ImmutableBiMap и ImmutableSortedMap (реализованные через builder-ы этих типов).

Если ваши идентификаторы написаны на обычном английском, то Lombok предполагает, что имя любой коллекции с @Singular — это английское слово во множественном числе, и пытается автоматически привести их к единственному числу. Если это возможно, то метод добавления одного значения использует это имя. Например, если ваша коллекция называется statuses, то метод добавления одного значения будет называться status. Вы также можете указать форму единственного числа для вашего идентификатора явно, передавая её в качестве аргумента аннотации @Singular("axis") List<Line> axes;.
Если Lombok не может привести к единственному числу имя вашего идентификатора, или оно неоднозначное, то Lombok генерирует ошибку и заставляет вас явно указать форму единственного числа.

Кусок кода ниже не показывает, что Lombok генерирует для @Singular поле/параметра потому что это довольно сильно усложнено.

С помощью Lombok

Чистая Java

Поддерживаемые ключи конфигурации

Lombok будет помечать любое использование @Builder предупреждением или ошибкой, если настроено.

 

Если true, то Lombok будет использовать ImmutableXxx builder-ы из guava для реализации интерфейсов java.util collection, вместо создания реализаций на основе Collections.unmodifiableXxx. Вы должны убедиться, что guava доступна в classpath и buildpath, если используете эту настройку. Guava используется автоматически, если ваше поле/параметр имеет один из типов ImmutableXxx.

 

Если true (по умолчанию), то Lombok пытается автоматически привести имя вашего идентификатора к единственному числу. Если false, то вы должны всегда явно указывать имя в единственном числе, и Lombok сгенерирует ошибку, если вы не сделаете этого (полезно если вы пишете ваш код на отличном от английского языке).

 

Примечания

Поддержка @Singular для java.util.NavigableMap/Set только тогда, когда вы компилируете с помощью JDK1.8 или более поздней версии.

Вы не можете создать вручную часть или все части узла @Singular, Lombok генерирует их слишком сложными. Если вы хотите контролировать вручную часть кода builder-а, связанного с каким-то полем или параметром, то не используйте @Singular и добавьте всё, что вам нужно, вручную.

Сортированные коллекции (java.util: SortedSet, NavigableSet, SortedMap, NavigableMap и guava: ImmutableSortedSet, ImmutableSortedMap) требуют того, чтобы элементы коллекции реализовывали интерфейс java.util.Comparable. Нет способа передачи явного Comparator-а в builder.

ArrayList используется для хранения добавленных элементов, добавляемых вызовами методов полей, помеченных @Singular, если целевая коллекция из пакета java.util, даже если коллекция является множеством (Set) или справочником (Map). Новый экземпляр Set или Map создаётся в любом случае, потому что Lombok гарантирует, что генерируемые коллекции плотно упакованы , и хранение данных в ArrayList во время процесса создания более эффективно, чем их хранение в Map или Set. Это поведение снаружи незаметно.

С toBuilder = true, применённого к статическим методам, любой тип параметра аннотированного статического метода должен показаться в возвращаемом типе.

 

Цикл статей «Project Lombok».

Следующая статья — «Lombok @SneakyThrows — храбро бросаем проверяемые исключения там, где их ещё никто не бросал».
Предыдущая статья — «Lombok @Value — лёгкое создание неизменяемых классов».

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *