Автор оригинала: Andrea Ligios.
1. Обзор
Библиотека Lombok предоставляет отличный способ реализовать шаблон Builder без написания шаблонного кода: аннотацию @Builder .
В этом коротком уроке мы специально узнаем , как работать с аннотацией @Builder , когда речь идет о наследовании . Мы продемонстрируем две техники. Один полагается на стандартные функции Ломбока. Другой использует экспериментальную функцию, введенную в Lombok 1.18.
Для более широкого обзора аннотации Builder мы можем обратиться к использованию аннотации @Builder Ломбока .
Подробный обзор библиотеки проекта Ломбок также доступен в разделе Введение в проект Ломбок .
2. Ломбок @Строитель и наследование
2.1. Определение проблемы
Предположим, что наш Дочерний класс расширяет Родительский класс:
@Getter @AllArgsConstructor public class Parent { private final String parentName; private final int parentAge; } @Getter @Builder public class Child extends Parent { private final String childName; private final int childAge; }
При использовании @Builder в классе, который расширяет другой подобный класс, мы получим следующую ошибку компиляции в аннотации:
Неявный суперконструктор Parent() не определен. Необходимо явно вызвать другой конструктор
Это связано с тем, что Ломбок не учитывает поля суперклассов, а только поля из текущего класса.
2.2. Решение проблемы
К счастью для нас, есть простой обходной путь. Мы можем создать (с помощью нашей IDE или даже вручную) конструктор на основе полей. Это включает в себя также поля из суперклассов. Мы аннотируем его с помощью @Builder вместо класса:
@Getter @AllArgsConstructor public class Parent { private final String parentName; private final int parentAge; } @Getter public class Child extends Parent { private final String childName; private final int childAge; @Builder public Child(String parentName, int parentAge, String childName, int childAge) { super(parentName, parentAge); this.childName = childName; this.childAge = childAge; } }
Таким образом, мы сможем получить доступ к удобному конструктору из класса Child , который позволит нам также указать поля Parent class:
Child child = Child.builder() .parentName("Andrea") .parentAge(38) .childName("Emma") .childAge(6) .build(); assertThat(child.getParentName()).isEqualTo("Andrea"); assertThat(child.getParentAge()).isEqualTo(38); assertThat(child.getChildName()).isEqualTo("Emma"); assertThat(child.getChildAge()).isEqualTo(6);
2.3. Сосуществование нескольких @Builders
В случае , если суперкласс сам аннотируется с помощью @Builder , мы получим следующую ошибку при аннотировании конструктора дочернего класса:
Возвращаемый тип несовместим с Parent.builder()
Это связано с тем, что класс Child пытается представить оба Builders с одинаковым именем.
Мы можем решить эту проблему, присвоив уникальное имя хотя бы одному из методов построителя:
@Getter public class Child extends Parent { private final String childName; private final int childAge; @Builder(builderMethodName = "childBuilder") public Child(String parentName, int parentAge, String childName, int childAge) { super(parentName, parentAge); this.childName = childName; this.childAge = childAge; } }
Затем мы сможем получить ParentBuilder через Child.builder() и ChildBuilder через Child.child Builder() .
2.4. Поддержка Более Крупных Иерархий Наследования
В некоторых случаях нам может потребоваться поддержка более глубоких иерархий наследования. Мы можем использовать тот же шаблон, что и раньше. Давайте создадим подкласс Child .
@Getter public class Student extends Child { private final String schoolName; @Builder(builderMethodName = "studentBuilder") public Student(String parentName, int parentAge, String childName, int childAge, String schoolName) { super(parentName, parentAge, childName, childAge); this.schoolName = schoolName; } }
Как и прежде, нам нужно вручную добавить конструктор. Это должно принимать все свойства из всех родительских классов и дочерних в качестве аргументов. Затем мы добавим аннотацию @Builder , как и раньше. Указав другое уникальное имя метода в аннотации, мы можем получить строители для Родителя , Ребенка или Ученика .
Student student = Student.studentBuilder() .parentName("Andrea") .parentAge(38) .childName("Emma") .childAge(6) .schoolName("Baeldung High School") .build(); assertThat(student.getChildName()).isEqualTo("Emma"); assertThat(student.getChildAge()).isEqualTo(6); assertThat(student.getParentName()).isEqualTo("Andrea"); assertThat(student.getParentAge()).isEqualTo(38); assertThat(student.getSchoolName()).isEqualTo("Baeldung High School");
Таким образом, мы можем расширить этот шаблон, чтобы иметь дело с любой глубиной наследования. Конструктор, который нам нужно создать, может стать довольно большим, но ваша IDE может вам помочь.
3. Lombok @SuperBuilder и наследование
Как мы уже отмечали ранее, версия 1.18 Lombok представила аннотацию @SuperBuilder . Мы можем использовать это, чтобы решить нашу проблему более простым способом.
3.1. Применение аннотаций
Мы можем сделать строителя, который может видеть свойства своих предков.
Для этого мы аннотируем наш класс и его предков аннотацией @SuperBuilder .
Давайте продемонстрируем здесь нашу трехуровневую иерархию. Обратите внимание, что принцип простого родительского и дочернего наследования одинаков:
@Getter @SuperBuilder public class Parent { // same as before... @Getter @SuperBuilder public class Child extends Parent { // same as before... @Getter @SuperBuilder public class Student extends Child { // same as before...
Когда все классы аннотируются таким образом, мы получаем конструктор для дочернего класса, который также предоставляет свойства родителей.
Обратите внимание, что мы должны аннотировать все классы. @SuperBuilder нельзя смешивать с @Builder в одной иерархии классов. Это приведет к ошибке компиляции.
3.2. Использование конструктора
На этот раз нам не нужно определять какие-либо специальные конструкторы. Класс builder, созданный @SuperBuilder , ведет себя так же, как и тот, который мы создали с помощью основного Ломбока @Builder :
Student student = Student.builder() .parentName("Andrea") .parentAge(38) .childName("Emma") .childAge(6) .schoolName("Baeldung High School") .build(); assertThat(student.getChildName()).isEqualTo("Emma"); assertThat(student.getChildAge()).isEqualTo(6); assertThat(student.getParentName()).isEqualTo("Andrea"); assertThat(student.getParentAge()).isEqualTo(38); assertThat(student.getSchoolName()).isEqualTo("Baeldung High School");
4. Заключение
Мы видели, как справиться с распространенными ловушками использования аннотации @Builder в классах, использующих наследование.
Если мы используем основную аннотацию Lombok @Builder , у нас есть несколько дополнительных шагов, чтобы заставить ее работать. Но если мы готовы использовать экспериментальные функции, то @SuperBuilder может упростить ситуацию.
Как всегда, полный исходный код доступен на Github .