Проект Lombok создан для уменьшения шаблонного (boilerplate) кода.
Ведь часто в классе нам требуется писать геттеры, сеттеры и конструкторы. С помощью Lombok можно этот процесс автоматизировать и генерировать код на этапе сборки проекта.
При этом кода на этапе разработки не существует. Чтобы среда разработки понимала, что код будет в дальнейшем существовать, необходимо настроить проект для работы с Lombok.
А ещё Lombok — это название острова в составе Индонезии, недалеко от острова Ява.
️ Требования #
Для подключения Lombok ваш проект должен использовать:
Java >= 1.8
Maven 3.6.x (Gradle >= 4)
Также все действия будут проводиться в среде разработки IntelliJ IDEA.
Добавление зависимостей в проект #
В примере будет показан проект с использованием Maven и среды разработки IntelliJ IDEA.
Добавьте в pom.xml зависимость:
org.projectlomboklombok1.18.24provided
Область видимости provided говорит, что в результирующем артефакте библиотеки Lombok не будет. Lombok на этапе сборки создаст код, и больше для работы приложения он будет не нужен.
Установите версию Java в pom.xml, в секцию properties . В таком случае Maven будет использовать данную версию для компиляции проекта, если значение не установлено, по умолчанию будет использоваться версия 1.5.
1111
Примените изменения pom.xml, для этого используйте горячие клавиши: Ctrl+Shift+O / ⌘ ⇧ I или обновите через контекстное меню папки проекта во вкладке Project и выберите Maven -> Reload project .
Если у вас версия IDEA выше 2020.3, то вам не требуется устанавливать дополнительные плагины. Плагин Lombok уже встроен в среду разработки.
В противном случае установите плагин Lombok из магазина плагинов в настройках IDEA.
▶️ Использование #
На примере нескольких аннотаций посмотрим, какие возможности даёт Lombok.
@Getter, @Setter #
Нам часто приходиться создавать геттеры и сеттеры для полей класса. Lombok может освободить от ручного создания таких методов и генерировать их при сборке проекта.
Добавив над классов аннотации @Getter , @Setter мы получим точно такие же сеттеры, названия методов по умолчанию формируется по соглашению getИмяПеременной , setИмяПеременной .
Сеттеры генерируются только для не final полей, геттеры — для всех полей.
@NoArgsConstructor, @AllArgsConstructor #
Таким же образом мы можем генерировать конструкторы класса. Допустим, нашему классу необходимы два конструктора: без аргументов и со всеми аргументами. Без Lombok мы напишем это так:
При использовании аннотации @AllArgsConstructor Lombok создаст конструктор для всех полей класса, аргументы будут располагаться в порядке объявления полей внутри класса:
Используя этот конструктор, мы напишем такую строку создания объекта Person:
Person person =new Person("Alex", 34);
@ToString #
Для переопределения метода toString() у класса также можно использовать Lombok. Аннотация на классом @ToString переопределит метод и сформирует строку из всех полей класса и имени класса.
Например, если мы добавим к нашему классу Person аннотацию @ToString:
Person person =new Person("Alex", 34); System.out.println(person);
то получим в консоли строку:
Person(name=Alex, age=34)
Не все поля нам нужны для toString() , и мы можем часть исключить. Для этого у аннотации есть дополнительный параметр exclude , в котором мы можем прописать список полей, не участвующих в формировании строки. Это полезно, когда нам надо скрыть чувствительные данные: пароли, персональные данные, которые могут попасть в логи или консоль.
У других аннотаций также есть дополнительные параметрами, с которыми при необходимости ознакомитесь в документации к Lombok.
@Data #
Есть аннотации, которые включают в себя другие аннотации. Это позволяет не писать множество аннотаций над классом, так как такие наборы аннотаций часто повторяются и @Data позволяет нам одной строкой добавить к классу:
@ToString
@Getter
@Setter
@RequiredArgsConstructor — конструктор для полей с модификатором final,
@EqualsAndHashCode — формирование методов equals() и hashCode() из всех полей
Для примера заменим часть аннотаций в Person на @Data :
Кода становится меньше, но если нам требуется настроить более тонко одну из аннотаций, входящих в состав @Data , то необходимо её написать отдельно — например, чтобы исключить поля из toString() .
Lombok — это библиотека для сокращения кода в классах и расширения функциональности языка Java. Подключается к среде разработки (IDE) или инструменту сборки приложений Maven, Gradle в качестве плагина.
Освойте профессию «Java-разработчик»
Библиотека получила название в честь индонезийского острова Ломбок, расположенного недалеко от острова Ява. В переводе с индонезийского Lombok означает «перец чили»: по аналогии с приправой, библиотека призвана повысить качество Java-кода.
Зачем нужен Lombok в Java
Java — один из популярных языков, но у него есть несколько недостатков. Один из них — необходимость написания шаблонного кода для геттеров, сеттеров, метода toString() и так далее. Например, Kotlin и Scala также работают поверх JVM, но не нуждаются в шаблонном коде. Это причина их растущей популярности. Использование Lombok позволяет автоматически преобразовать объемный Java-код в оптимизированную и лаконичную структуру, характерную для перечисленных языков.
Также плагин избавляет разработчика от ручного набора однотипных фрагментов кода и предотвращает появление ошибок.
Профессия / 14 месяцев
Java-разработчик
Освойте востребованный язык
Как работает Lombok
Принцип работы библиотеки основан на использовании аннотаций, которые:
многократно сокращают шаблонный код;
экономят время разработчиков;
улучшают читаемость кода.
Сравним «чистый» Java-код и код, оптимизированный с помощью библиотеки Lombok.
Обычный Java-код
Код, использующий аннотации Lombok
public class Employee private Integer employeeId; private String name; private String company; private String emailId; public Employee() <> public Employee(Integer employeeId, String name, String company, String emailId) super(); this.employeeId = employeeId; this.name = name; this.company = company; this.emailId = emailId; > public Integer getEmployeeId() return employeeId; > public void setEmployeeId(Integer employeeId) this.employeeId = employeeId; > public String getName() return name; > public void setName(String name) this.name = name; > public String getCompany() return company; > public void setCompany(String company) this.company = company; > public String getEmailId() return emailId; > public void setEmailId(String emailId) this.emailId = emailId; > @Override public String toString() return «Employee [» + «employeeId=» + employeeId + «, name=» + name + «, » + » company=» + company + «, emailId=» + emailId + «]»; > >
Станьте Java-разработчиком и создавайте сложные сервисы на востребованном языке
Аннотации в Lombok
Наиболее полезные аннотации в библиотеке:
@Getter и @Setter — предоставляют геттеры и сеттеры для поля. Могут использоваться как на уровне поля, так и на уровне класса.
@NoArgsConstructor — используется для создания конструктора без аргументов.
@AllArgsConstructor — генерирует параметризованный конструктор, который принимает один параметр для каждого поля и инициализирует их с его помощью. Аннотация необходима, если нужно создать объект класса, передав начальные значения полей в конструктор.
@ToString — переопределяет метод toString() и создает для него реализацию по умолчанию. Она выводит имя класса и поля по порядку, разделяя их запятыми. Можно пропустить отдельные поля, если аннотировать их с помощью @ToString.Exclude.
@EqualsAndHashCode — применяется для переопределения методов equals() и hashCode().
@Data — объединяет аннотации @ToString, @Getter, @Setter, @EqualsAndHashCode и @RequiredArgsConstructor в одну. Предоставляет весь код, который обычно используется в классах моделей, например, геттеры для всех полей, сеттеры для всех нефинальных полей, реализацию по умолчанию для toString(), equals() и hashCode(), а также конструктор, который инициализирует все поля класса.
Java-разработчик
Java уже 20 лет в мировом топе языков программирования. На нем создают сложные финансовые сервисы, стриминги и маркетплейсы. Освойте технологии, которые нужны для backend-разработки, за 14 месяцев.
Статьи по теме:
Lombok возвращает величие Java
Мы в Grubhub почти во всём бэкенде используем Java. Это проверенный язык, который за последние 20 лет доказал свою скорость и надёжность. Но с годами возраст «старичка» всё-таки начал сказываться.
Java — один из самых популярных языков JVM, но не единственный. В последние годы конкуренцию ему составляют Scala, Clojure и Kotlin, которые обеспечивают новую функциональность и оптимизированные функции языка. Короче говоря, они позволяют делать больше с более лаконичным кодом.
Эти инновации в экосистеме JVM очень интересные. Из-за конкуренции Java вынуждена меняться, чтобы сохранить конкурентоспособность. Новый шестимесячный график выпуска и несколько JEP (JDK enhancement proposals) в Java 8 (Valhalla, local-Variable Type Inference, Loom) — доказательство того, что Java долгие годы останется конкурентоспособным языком.
Тем не менее, размер и масштаб Java означают, что разработка продвигается медленнее, чем мы хотели бы, не говоря уже о сильном желании любой ценой поддерживать обратную совместимость. В любой разработке первым приоритетом должны быть функции, однако здесь необходимые функции слишком долго разрабатываются, если вообще попадают в язык. Поэтому мы в Grubhub используем Project Lombok, чтобы прямо сейчас иметь в своём распоряжении оптимизированную и улучшенную Java. Проект Lombok — это плагин компилятора, который добавляет в Java новые «ключевые слова» и превращает аннотации в Java-код, уменьшая усилия на разработку и обеспечивая некоторую дополнительную функциональность.
Настройка Lombok
Grubhub всегда стремится улучшить жизненный цикл программного обеспечения, но каждый новый инструмент и процесс имеет стоимость, которую следует учесть. К счастью, для подключения Lombok достаточно добавить всего пару строк в файл gradle.
Lombok преобразует аннотации в исходном коде в Java-операторы до того, как компилятор их обработает: зависимость lombok отсутствует в рантайме, поэтому использование плагина не увеличит размер сборки. Чтобы настроить Lombok с Gradle (он также работает с Maven), просто добавьте в файл build.gradle такие строки:
plugins < id 'io.franzbecker.gradle-lombok' version '1.14' id 'java' >repositories < jcenter() // or Maven central, required for Lombok dependency >lombok
При использовании Lombok наш исходный код не будет валидным кодом Java. Поэтому потребуется установить плагин для IDE, иначе среда разработки не поймёт, с чем имеет дело. Lombok поддерживает все основные Java IDE. Интеграция бесшовная. Все функции вроде «показать использования» и «перейти к реализации» продолжают работать как и раньше, перемещая вас к соответствующему полю/классу.
Lombok в действии
Лучший способ познакомиться с Lombok — увидеть его в действии. Рассмотрим несколько типичных примеров.
Оживить объект POJO
При помощи «старых добрых объектов Java» (POJO) мы отделяем данные от обработки, чтобы сделать код проще для чтения и упростить сетевые передачи. В простом POJO есть несколько приватных полей, а также соответствующие геттеры и сеттеры. Они справляются с работой, но требуют большого количества шаблонного кода.
Lombok помогает использовать POJO более гибким и структурированным образом без дополнительного кода. Вот так с помощью аннотации @Data мы упрощаем базовый POJO:
@Data public class User
@Data — просто удобная аннотация, которая применяет сразу несколько аннотаций Lombok.
@ToString генерирует реализацию для метода toString() , которая состоит из аккуратного представления объекта: имя класса, все поля и их значения.
@EqualsAndHashCode генерирует реализации equals и hashCode , которые по умолчанию используют нестатические и нестационарные поля, но настраиваются.
@Getter / @Setter генерирует геттеры и сеттеры для частных полей.
@RequiredArgsConstructor создаёт конструктор с требуемыми аргументами, где обязательными являются окончательные поля и поля с аннотацией @NonNull (подробнее об этом ниже).
Есть другое решение. Вернёмся к нашему классу User , сделаем его неизменяемым и добавим несколько других полезных аннотаций.
@Value @Builder(toBuilder = true) public class User < @NonNull UUID userId; @NonNull String email; @Singular SetfavoriteFoods; @NonNull @Builder.Default String avatar = “default.png”; >
Аннотация @Value аналогична @Data за исключением того, что все поля по умолчанию являются закрытыми и окончательными, а сеттеры не создаются. Благодаря этому объекты @Value сразу становятся неизменяемыми. Поскольку все поля являются окончательными, конструктора аргументов нет. Вместо этого Lombok использует @AllArgsConstructor . В результате получается полностью функциональный, неизменяемый объект.
Но неизменяемость не очень полезна, если вам нужно всего лишь создать объект с помощью конструктора all-args. Как объясняет Джошуа Блох в книге «Эффективное программирование на Java», при наличии большого количества параметров конструктора следует использовать билдеры. Тут вступает в действие класс @Builder , автоматически генерируя внутренний класс билдера:
User user = User.builder() .userId(UUID.random()) .email(“grubhub@grubhub.com”) .favoriteFood(“burritos”) .favoriteFood(“dosas”) .build()
Генерация билдера упрощает создание объектов с большим количеством аргументов и добавлением новых полей в будущем. Статический метод возвращает экземпляр билдера для задания всех свойств объекта. После этого вызов build() возвращает инстанс.
Аннотацию @NonNull можно использовать для утверждения, что эти поля не являются нулевыми при создании экземпляра объекта, иначе выбрасывается исключение NullPointerException . Обратите внимание, что поле аватара аннотировано @NonNull , но не задано. Дело в том, что аннотация @Builder.Default по умолчанию указывает на default.png.
Также обратите внимание, как билдер использует favoriteFood , единственное название свойства в нашем объекте. При размещении аннотации @Singular на свойстве коллекции Lombok создаёт специальные методы билдера для индивидуального добавления элементов в коллекцию, а не для одновременного добавления всей коллекции. Это особенно хорошо для тестов, потому что способы создания маленьких коллекций в Java нельзя назвать простыми и быстрыми.
Наконец, параметр toBuilder = true добавляет метод экземпляра toBuilder() , который создаёт объект билдера, заполненный всеми значениями этого экземпляра. Так легко создаётся новый инстанс, предварительно заполненный всеми значениями из исходного, так что остаётся изменить лишь необходимые поля. Это особенно полезно для классов @Value , поскольку поля неизменяемы.
Несколько примечаний дополнительно настраивают специальные функции сеттера. @Wither создаёт методы withX для каждого свойства. На входе — значение, на выходе — клон экземпляра с обновлённым значением одного поля. @Accessors позволяет настраивать автоматически созданные сеттеры. Параметр fluent=true отключает конвенцию “get” и “set” для геттеров и сеттеров. В определённых ситуациях это может быть полезной заменой @Builder .
Если реализация Lombok не подходит для вашей задачи (и вы посмотрели на модификаторы аннотаций), то всегда можно просто взять и написать собственную реализацию. Например, если у вас класс @Data , но один геттер нуждается в пользовательской логике, просто реализуйте этот геттер. Lombok увидит, что реализация уже предоставлена, и не перезапишет её автоматически созданной реализацией.
С помощью всего нескольких простых аннотаций базовый POJO получил так много богатых функций, которые упрощают его использование, не загружая работой нас, инженеров, не отнимая время и не увеличивая затраты на разработку.
Удаление шаблонного кода
Lombok полезен не только для POJO: его можно применить на любом уровне приложения. Следующие способы использования Lombok особенно полезны в классах компонентов, таких как контроллеры, службы и DAO (объекты доступа к данным).
Ведение журнала — базовое требование для всех частей программы. Любой класс, выполняющий значимую работу, должен записывать лог. Таким образом, стандартный логгер становится шаблоном для каждого класса. Lombok упрощает этот шаблон до одной аннотации, которая автоматически определяет и создаёт экземпляр логгера с правильным именем класса. Существует несколько различных аннотаций в зависимости от структуры журнала.
@Slf4j // also: @CommonsLog @Flogger @JBossLog @Log @Log4j @Log4j2 @XSlf4j public class UserService < // created automatically // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserService.class); >
После объявления логгера добавляем наши зависимости:
@Slf4j @RequiredArgsConstructor @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) public class UserService
Аннотация @FieldDefaults добавляет ко всем полям окончательный и приватный модификаторы. @RequiredArgsConstructor создаёт конструктор, который устанавливает экземпляр UserDao . Аннотация @NonNull добавляет проверку в конструкторе и создаёт исключение NullPointerException , если экземпляр UserDao равен нулю.
Но подождите, это ещё не всё!
Есть ещё много ситуаций, где Lombok проявляет себя с лучшей стороны. Предыдущие разделы показывали конкретные примеры, но Lombok может облегчить разработку во многих областях. Вот несколько небольших примеров, как эффективнее его использовать.
Хотя в Java 9 появилось ключевое слово var , но переменную всё равно можно переназначить. В Lombok есть ключевое слово val , которое выводит окончательный тип локальной переменной.
// final Map map = new HashMap(); val map = new HashMap();
Некоторые классы c чисто статическими функциями не предназначены для инициализации. Один из способов предотвратить создание экземпляра — объявить приватный конструктор, который выбрасывает исключение. Lombok кодифицировал этот шаблон в аннотации @UtilityClass . Она генерирует приватный конструктор, который создаёт исключение, окончательно выводит класс и делает все методы статическими.
@UtilityClass // will be made final public class UtilityClass < // will be made static private final int GRUBHUB = “ GRUBHUB”; // autogenerated by Lombok // private UtilityClass() < // throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); //>// will be made static public void append(String input) < return input + GRUBHUB; >>
Java часто критикуют за многословность из-за проверяемых исключений. Отдельная аннотация Lombok устраняет их: @SneakyThrows . Как и следовало ожидать, реализация довольно хитрая. Она не перехватывает исключения и даже не оборачивает исключения в RuntimeException . Вместо этого она полагается на тот факт, что во время выполнения JVM не проверяет согласованность проверяемых исключений. Так делает только javac. Поэтому Lombok с помощью преобразования байт-кода во время компиляции отключает эту проверку. В результате получается запускаемый код.
public class SneakyThrows < @SneakyThrows public void sneakyThrow() < throw new Exception(); >>
Сравнение бок о бок
Прямое сравнение лучше всего демонстрирует, сколько кода экономит Lombok. В плагине IDE есть функция “de-lombok”, которая приблизительно преобразует большинство аннотаций Lombok в нативный Java-код (аннотации @NonNull не конвертируются). Таким образом, любая IDE с установленным плагином сможет конвертировать большинство аннотаций в собственный код Java и обратно. Вернёмся к нашему классу User .
@Value @Builder(toBuilder = true) public class User < @NonNull UUID userId; @NonNull String email; @Singular SetfavoriteFoods; @NonNull @Builder.Default String avatar = “default.png”; >
Класс Lombok — всего лишь 13 простых, читаемых, понятных строк. Но после запуска de-lombok, класс превращается более чем в сто строк шаблонного кода!
public class User < @NonNull UUID userId; @NonNull String email; SetfavoriteFoods; @NonNull @Builder.Default String avatar = "default.png"; @java.beans.ConstructorProperties() User(UUID userId, String email, Set favoriteFoods, String avatar) < this.userId = userId; this.email = email; this.favoriteFoods = favoriteFoods; this.avatar = avatar; >public static UserBuilder builder() < return new UserBuilder(); >@NonNull public UUID getUserId() < return this.userId; >@NonNull public String getEmail() < return this.email; >public Set getFavoriteFoods() < return this.favoriteFoods; >@NonNull public String getAvatar() < return this.avatar; >public boolean equals(Object o) < if (o == this) return true; if (!(o instanceof User)) return false; final User other = (User) o; final Object this$userId = this.getUserId(); final Object other$userId = other.getUserId(); if (this$userId == null ? other$userId != null : !this$userId.equals(other$userId)) return false; final Object this$email = this.getEmail(); final Object other$email = other.getEmail(); if (this$email == null ? other$email != null : !this$email.equals(other$email)) return false; final Object this$favoriteFoods = this.getFavoriteFoods(); final Object other$favoriteFoods = other.getFavoriteFoods(); if (this$favoriteFoods == null ? other$favoriteFoods != null : !this$favoriteFoods.equals(other$favoriteFoods)) return false; final Object this$avatar = this.getAvatar(); final Object other$avatar = other.getAvatar(); if (this$avatar == null ? other$avatar != null : !this$avatar.equals(other$avatar)) return false; return true; >public int hashCode() < final int PRIME = 59; int result = 1; final Object $userId = this.getUserId(); result = result * PRIME + ($userId == null ? 43 : $userId.hashCode()); final Object $email = this.getEmail(); result = result * PRIME + ($email == null ? 43 : $email.hashCode()); final Object $favoriteFoods = this.getFavoriteFoods(); result = result * PRIME + ($favoriteFoods == null ? 43 : $favoriteFoods.hashCode()); final Object $avatar = this.getAvatar(); result = result * PRIME + ($avatar == null ? 43 : $avatar.hashCode()); return result; >public String toString() < return "User(userId=" + this.getUserId() + ", email=" + this.getEmail() + ", favoriteFoods=" + this.getFavoriteFoods() + ", avatar=" + this.getAvatar() + ")"; >public UserBuilder toBuilder() < return new UserBuilder().userId(this.userId).email(this.email).favoriteFoods(this.favoriteFoods).avatar(this.avatar); >public static class UserBuilder < private UUID userId; private String email; private ArrayListfavoriteFoods; private String avatar; UserBuilder() < >public User.UserBuilder userId(UUID userId) < this.userId = userId; return this; >public User.UserBuilder email(String email) < this.email = email; return this; >public User.UserBuilder favoriteFood(String favoriteFood) < if (this.favoriteFoods == null) this.favoriteFoods = new ArrayList(); this.favoriteFoods.add(favoriteFood); return this; > public User.UserBuilder favoriteFoods(Collection favoriteFoods) < if (this.favoriteFoods == null) this.favoriteFoods = new ArrayList(); this.favoriteFoods.addAll(favoriteFoods); return this; > public User.UserBuilder clearFavoriteFoods() < if (this.favoriteFoods != null) this.favoriteFoods.clear(); return this; >public User.UserBuilder avatar(String avatar) < this.avatar = avatar; return this; >public User build() < SetfavoriteFoods; switch (this.favoriteFoods == null ? 0 : this.favoriteFoods.size()) < case 0: favoriteFoods = java.util.Collections.emptySet(); break; case 1: favoriteFoods = java.util.Collections.singleton(this.favoriteFoods.get(0)); break; default: favoriteFoods = new java.util.LinkedHashSet(this.favoriteFoods.size() < 1073741824 ? 1 + this.favoriteFoods.size() + (this.favoriteFoods.size() - 3) / 3 : Integer.MAX_VALUE); favoriteFoods.addAll(this.favoriteFoods); favoriteFoods = java.util.Collections.unmodifiableSet(favoriteFoods); >return new User(userId, email, favoriteFoods, avatar); > public String toString() < return "User.UserBuilder(userId=" + this.userId + ", email=" + this.email + ", favoriteFoods=" + this.favoriteFoods + ", avatar=" + this.avatar + ")"; >> >
То же самое сделаем для класса UserService .
@Slf4j @RequiredArgsConstructor @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) public class UserService
Вот примерный аналог в стандартном Java-коде.
public class UserService < private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserService.class); private final UserDao userDao; @java.beans.ConstructorProperties() public UserService(UserDao userDao) < if (userDao == null) < throw new NullPointerException("userDao is marked @NonNull but is null") >this.userDao = userDao; > >
Оценка эффекта
На портале Grubhub более ста бизнес-сервисов, связанных с доставкой еды. Мы взяли один из них и запустили функцию “de-lombok” в плагине Lombok IntelliJ. В результате изменилось около 180 файлов, а кодовая база выросла примерно на 18 000 строк кода после удаления 800 случаев использований Lombok. В среднем, каждая строка Lombok экономит 23 строки Java. С таким эффектом трудно представить Java без Lombok.
Резюме
Lombok — отличный помощник, который реализует новые функции языка, не требуя особых усилий со стороны разработчика. Конечно, проще установить плагин, чем обучить всех инженеров новому языку и портировать существующий код. Lombok не всесилен, но уже из коробки достаточно мощный, чтобы реально помочь в работе.
Ещё одно преимущество Lombok в том, что он сохраняет согласованность кодовых баз. У нас более ста различных сервисов и распределённая команда по всему миру, так что согласованность кодовых баз облегчает масштабирование команд и снижает нагрузку на переключение контекста при запуске нового проекта. Lombok работает для любой версии начиная с Java 6, поэтому мы можем рассчитывать на его доступность во всех проектах.
Для Grubhub это больше, чем просто новые функции. В конце концов, весь этот код можно написать вручную. Но Lombok упрощает скучные части кодовой базы, не влияя на бизнес-логику. Это позволяет сфокусироваться на вещах, действительно важных для бизнеса и наиболее интересных для наших разработчиков. Монтонный шаблонный код — это пустая трата времени программистов, рецензентов и мейнтейнеров. Кроме того, поскольку этот код больше не пишется вручную, то устраняет целые классы опечаток. Преимущества автогенерации в сочетании с мощью @NonNull уменьшают вероятность ошибок и помогают нашей разработке, которая направлена на доставку еды к вашему столу!
Java
Проектирование и рефакторинг
Lombok. Полное руководство
Здесь изложен необходимый минимум информации, которую нужно изучить, если хочешь приступить к использованию проекта Lombok. Рассмотрим, как интегрировать его в вашу IDE и использовать, чтобы сократить объем шаблонного кода.
Java – отличный язык, только многословный. Возможно, вам придется писать много кода, чтобы достичь даже самых простых целей. Кроме того, в Java определенно присутствует повторяющийся код, например, геттеры и сеттеры. Поэтому у вас получаются огромные объемы повторяющегося и необязательного кода. Мало того, что такой код не добавляет ничего нового в бизнес-логику вашего приложения, так и писать его долго и скучно. Именно поэтому следует переходить к использованию библиотек и инструментов – они помогают повысить продуктивность и избежать этой рутины. Именно здесь в игру вступает Lombok!
Это библиотека Java, в которой предоставляется ряд аннотаций, направленных на исключение именно того кода Java, о котором известно, что он часто становится повторяющимся и/или шаблонным. Проект Lombok включается прямо в процесс сборки. Затем Lombok автоматически сгенерирует для Java байт-код, который вставляет в файлы .class, необходимые для реализации желаемого поведения, в зависимости от используемых вами аннотаций. Следовательно, каждая аннотация, предлагаемая в проекте Lombok, позволяет частично обойтись без написания методов и логики, без которых вы хотели бы обойтись. Речь о конструкторах, равенствах и функциях хеш-кода. Так вы сможете сэкономить массу времени и сосредоточиться на бизнес-логике вашего проекта. Кроме того, вы сможете держать базу кода сравнительно компактной, чистой, удобной для чтения и поддержки.
Сначала рассмотрим, что представляет собой проект Lombok и как он работает. Затем изучим наиболее распространенные и актуальные аннотации Lombok, разберемся, какие из них наиболее важные, где и как их использовать. Далее рассмотрим, как интегрировать их в вашу IDE (интегрированную среду разработки) и почему не стоит опасаться их использовать.
Предпосылки
Вот что вам нужно установить, чтобы воспроизвести все примеры, приведенные ниже:
Java >= 1.8
Gradle >= 4.x or Maven 3.6.x
Project Lombok >= 1.18.20
Что такое Lombok
Проект Lombok (далее – просто Lombok) – это основанная на аннотациях библиотека Java, позволяющая сократить шаблонный код. В Lombok предлагаются различные аннотации, цель которых – заменить ненужный повторяющийся код, писать который утомительно. Например, Lombok избавит вас от написания безаргументных конструкторов, методов toString(), equals() и hashCode(), для этого нужно просто добавить несколько аннотаций. Плюс, как будет показано ниже, эта библиотека подключается прямо к вашей IDE, поэтому работа воспринимается так, словно вы написали весь шаблонный код сами.
Установить Lombok легко – добавьте lombok к вашим зависимостям. Если вы пользуетесь Gradle, допишите следующие две строки с зависимостями в ваш файл build . gradle :
Теперь вы настроили все необходимое, чтобы приступить к работе с Lombok.
Наиболее распространенные аннотации Lombok
Ниже перечислены самые распространенные и важные аннотации Lombok. Каждая из них будет объяснена и рассмотрена на практике в сравнении с эквивалентным «переводом» на обычный язык Java. Чтобы посмотреть примеры и получить дополнительную поддержку, можно щелкнуть по любой аннотации и посетить посвященную ей страницу на сайте с официальной документацией по Lombok.
@Getter, @Setter
Когда после аннотировано @Getter и/или @Setter , Lombok автоматически сгенерирует заданный по умолчанию геттер и/или сеттер, соответственно. Заданная по умолчанию реализация геттеров просто берет на себя возврат аннотированного поля. Аналогично, заданная по умолчанию реализация сеттеров принимает один параметр того же типа, что и аннотированное поле, и просто устанавливает его в полученное значение. Когда поле под названием value аннотируется одновременно @Getter и @Setter , Lombok определит метод getValue() (или isValue(), если это поле типа boolean ) и setValue(). Сгенерированный метод, действующий как геттер и сеттер, будет публичным (public), если не указан конкретный уровень доступа ( AccessLevel ). Допустимые значения AccessLevel – это PUBLIC, PROTECTED, PACKAGE и PRIVATE. Обратите внимание: можно аннотировать и целый класс. В таком случае данная логика будет применяться к каждому из его полей.
С Lombok
@Getter @Setter public class Author
Просто Java
public class User < private int id; private String name; private String surname; public int getId() < return id; >public void setId(int id) < this.id = id; >public String getName() < return name; >public void setName(String name) < this.name = name; >public String getSurname() < return surname; >protected void setSurname(String surname) < this.surname = surname; >>
Когда класс аннотирован @NoArgsConstructor , Lombok возьмет на себя автоматическую генерацию непараметризованного конструктора. Аналогично, когда класс аннотирован @AllArgsConstructor , будет сгенерирован конструктор с одним параметром для каждого поля вашего класса. Далее, @RequiredArgsConstructor позволит получить конструктор с параметром для каждого поля, но эти параметры потребуют специальной обработки. В частности, здесь мы будем работать с неинициализированными полями final , а также с любыми полями, помеченными как @NonNull , которые не инициализируются при объявлении. Пожалуйста, не забудьте, что статические поля будут этими аннотациями игнорироваться.
С Lombok
@NoArgsConstructor @AllArgsConstructor @RequiredArgsConstructor public class Author
Просто Java
public class Author < private int id; private String name; private String surname; private final String birthPlace; // @NoArgsConstructor public Author() <>// @AllArgsConstructor public Author(int id, String name, String surname, String birthPlace) < this.id = id this.name = name this.surname = surname this.birthPlace = birthPlace >// @RequiredArgsConstructor public Author(String birthPlace) < this.birthPlace = birthPlace >>
@ToString
Когда класс аннотирован @ToString , Lombok позаботится о том, чтобы сгенерировать нужную реализацию метода toString(). По умолчанию будет возвращаться строка, в которой содержится имя класса, за которой следуют значения всех полей, разделенные запятыми. Устанавливая параметр includeFieldNames в значение true, добьемся того, что имя каждого поля будет ставиться перед его значением. По умолчанию при генерировании метода toString() будут учитываться все нестатические поля. Если мы хотим, чтобы Lombok проигнорировал поле, нужно аннотировать его @ToString . Exclude . Как вариант, можете сами указать, какие поля на ваше усмотрение должны аннотироваться, для этого воспользуйтесь @ ToString( onlyExplicitlyIncluded = true). Затем пометьте при помощи @ToString . Include каждое поле, которое хотите включить.
С Lombok
@ToString(includeFieldNames=true) public class Author
Просто Java
public class Author < private int id; private String name; private String surname; @Override public String toString() < return "Author(id=" + this.id + ", name=" + this.name + ", surnname=" + this.surname + ")"; >>
@EqualsAndHashCode
Если аннотировать класс при помощи @EqualsAndHashCode , то Lombok автоматически реализует за вас методы equals() и hashCode(). По умолчанию будут учитываться все нестатические непереходные поля. Можно повлиять на то, какие поля будут использоваться, аннотировав их @EqualsAndHashCode . Include или @EqualsAndHashCode . Exclude . Как вариант, можете аннотировать ваш класс при помощи @ EqualsAndHashCode( onlyExplicitlyIncluded = true), а затем в точности указать, какие поля или методы вы хотите использовать, пометив их @EqualsAndHashCode . Include . Обратите внимание: Lombok сгенерирует методы equals() и hashCode(), не нарушая контракта между ними. Перейдите по ссылкам о двух этих методах на сайт с официальной документацией Java и почитайте подробнее о тех контрактах, которым должны удовлетворять реализации equals() и hashCode().
С Lombok
@Getter @Setter @EqualsAndHashCode public class Author
Просто Java
public class Author < // геттеры и сеттеры . @Override public int hashCode() < final int PRIME = 31; int result = 1; result = prime * result + id; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((surname == null) ? 0 : surname.hashCode()); return result; >@Override public boolean equals(Object o) < if (o == this) return true; if (!(o instanceof Author)) return false; Author other = (Author) o; if (!other.canEqual((Object)this)) return false; if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false; if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false; if (this.getSurname() == null ? other.getSurname() != null : !this.getSurname().equals(other.getSurname())) return false; return true; >>
@NonNull
Можно аннотировать при помощи @NonNull компонент записи, параметр, метод или целый конструктор, В таком случае Lombok сгенерирует для вас команды для проверки на нуль.
С Lombok
public class Author < private int id; private String name; private String surname; public Author( @NonNull int id, @NonNull String name, String surname ) < this.id = id; this.name = name; this.surname = surname; >>
Просто Java
public class Author < private int id; private String name; private String surname; public Author( int id, String name, String surname ) < if (id == null) < throw new NullPointerException("id is marked @NonNull but is null"); >this.id = id; if (name == null) < throw new NullPointerException("name is marked @NonNull but is null"); >this.name = name; this.surname = surname; > >
@Data
@Data – это сокращенная аннотация, сочетающая возможности @ToString , @EqualsAndHashCode , @Getter @Setter и @RequiredArgsConstructor . Так что @Data генерирует весь шаблонный код, вовлеченный в работу с объектами POJO (Plain Old Java Objects). Это, в частности, дает нам геттеры для всех полей, сеттеры для всех нефинальных полей, правильные реализации toString , equals и hashCode , охватывающие все поля класса, а также конструктор для всех финальных полей.
С Lombok
@Data public class Author
Просто Java
public class Author < private final int id; private String name; private String surname; public Author(int id) < this.id = id; >public int getId() < return id; >public String getName() < return name; >public void setName(String name) < this.name = name; >public String getSurname() < return surname; >public void setSurname(String surname) < this.surname = surname; >@Override public int hashCode() < final int PRIME = 31; int result = 1; result = prime * result + getId(); result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); result = prime * result + ((getSurname() == null) ? 0 : getSurname().hashCode()); return result; >@Override public boolean equals(Object o) < if (o == this) return true; if (!(o instanceof Author)) return false; Author other = (Author) o; if (!other.canEqual((Object)this)) return false; if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false; if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false; if (this.getSurname() == null ? other.getSurname() != null : !this.getSurname().equals(other.getSurname())) return false; return true; >>
@Value
@Value – это неизменяемый вариант @Data . С этой аннотацией Lombok по умолчанию делает все поля private и final . Кроме того, сеттеры не генерируются, а класс как таковой помечается final . Таким образом, от этого класса нельзя наследовать. Точно как и в случае с @Data , создаются реализации toString(), equals() и hashCode().
С Lombok
@Data public class Author
Просто Java
public final class Author < private final int id; private final String name; private final String surname; public Author(int id, String name, String surname) < this.id = id this.name = name this.surname = surname >public int getId() < return id; >public String getName() < return name; >public String getSurname() < return surname; >@Override public int hashCode() < final int PRIME = 31; int result = 1; result = prime * result + getId(); result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); result = prime * result + ((getSurname() == null) ? 0 : getSurname().hashCode()); return result; >@Override public boolean equals(Object o) < if (o == this) return true; if (!(o instanceof Author)) return false; Author other = (Author) o; if (!other.canEqual((Object)this)) return false; if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false; if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false; if (this.getSurname() == null ? other.getSurname() != null : !this.getSurname().equals(other.getSurname())) return false; return true; >>
Продвинутые аннотации Lombok
Ниже рассмотрены самые сложные аннотации Lombok. Подробнее о каждой из них рассказано на собственной странице в официальной документации Lombok.
@Cleanup
Аннотация @Cleanup позволяет гарантировать, что заданный ресурс будет автоматически очищаться перед тем, как покинет актуальную область видимости. По умолчанию предполагается, что метод очистки аннотированного ресурса будет close(), но вы можете сами указать имя того метода, который, по вашему желанию, будет вызываться вместо него. Обратите внимание: эта аннотация активно использует команды try-with-resources.
С Lombok
public class CleanupDemo < public static void main(String[] args) throws IOException < @Cleanup InputStream input = new FileInputStream(args[0]); @Cleanup OutputStream output = new FileOutputStream(args[1]); byte[] b = new byte[10000]; while (true) < int r = input.read(b); if (r == -1) break; output.write(b, 0, r); >> >
Просто Java
public class CleanupDemo < public static void main(String[] args) throws IOException < try (OutputStream output = new FileOutputStream(args[1])) < try (InputStream input = new FileInputStream(args[0])) < byte[] b = new byte[10000]; while (true) < int r = input.read(b); if (r == -1) break; output.write(b, 0, r); >> > > >
@Synchronized
Аннотация @Synchronized действует примерно так же, как и ключевое слово synchronized , но защелкивается на других объектах. Ключевое слово защелкивается на this, а эта аннотация – на особом приватном поле под названием $lock . Если это поле не существует, то Lombok создаст его. Такое поведение задано по умолчанию, но вы можете и сами указать те объекты, на которых будет происходить защелкивание. При работе с методами static аннотация будет защелкиваться на статическом поле $ LOCK. Учтите, что эта аннотация, как и ключевое слово synchronized , может использоваться только со статическими методами и методами экземпляра.
С Lombok
public class SynchronizedDemo < private final Object objectToLock = new Object(); @Synchronized public static void sayHello() < System.out.println("Hello!"); >@Synchronized public int getOne() < return 1; >@Synchronized("objectToLock") public void printObject() < System.out.println(objectToLock); >>
Просто Java
public class SynchronizedDemo < private static final Object $LOCK = new Object[0]; private final Object $lock = new Object[0]; private final Object readLock = new Object(); public static void sayHello() < synchronized($LOCK) < System.out.println("Hello"); >> public int getOne() < synchronized($lock) < return 1; >> public void printObject() < synchronized(readLock) < System.out.println(objectToLock); >> >
@SneakyThrows
Аннотация @SneakyThrows позволяет бесшумно выбрасывать проверяемые исключения, не объявляя их явно в условии throws вашего метода, как принято делать. Итак, эта аннотация позволяет вам полностью избавиться от (как правило, необходимых в таких случаях) блоков try- catch , поскольку тихо обрабатывает все проверяемые исключения. В противном случае она запутала бы компилятор. Фактически, на уровне файлов классов JVM (виртуальной машины Java), все исключения могут выбрасываться безотносительно условия throws , присутствующего в ваших методах, поэтому-то данный механизм и работает. Вот почему стоит прочитать эту страницу из официальной документации Lombok, чтобы подробнее разобраться в этой аннотации и понять, как ею пользоваться.
@Builder
Возможно, вам потребуется разработать объект-строитель, который позволял бы вам создавать объекты, следуя пошаговой процедуре, например, Author .builder().id(«1»).name(«Maria»).surname(«Williams»).build();. Это особенно полезно, когда имеешь дело с большими классами, в каждом из которых по несколько полей. Вместо использования конструктора со многими полями, можно попробовать этот подход, более удобочитаемый. При помощи аннотации @Builder вы поручаете Lombok генерировать строители за вас. Аннотируя класс при помощи @Builder , Lombok выдает класс, реализующий вышеупомянутый паттерн «строитель». Например, аннотируя ею класс Author , получим автоматически сгенерированный класс AuthorBuilder . Поскольку поведение вашего строителя может быть сложным или сильно подогнанным под задачу, Lombok предлагает много параметров, при помощи которых можно достичь желаемого результата. Все они находятся здесь.
@Log
Большинство средств логирования требуют закладывать экземпляр логгера для каждого класса, в котором вы хотите вести лог. Разумеется, при этом возникает шаблонный код. Lombok, аннотируя класс при помощи @Log , автоматически добавляет статическое финальное поле log , инициализируемое вашей библиотекой логирования как требуемое. Вот почему Lombok дает разработчикам по аннотации на каждый из наиболее популярных фреймворков логирования. Их полный список находится здесь.
Плагин Lombok
К наиболее популярным и широко используемым IDE прилагается официальный плагин Lombok, предназначенный именно для упрощения работы с Lombok. В частности, он предлагает шорткаты для наиболее распространенных аннотаций Lombok. Кроме того, подсказывает, какие аннотации вам могут потребоваться или заинтересовать вас, в зависимости от того, где вы щелкаете мышью. На момент написания оригинала официально поддерживались IntelliJ IDEA, Eclipse, Spring Tool Suite, (Red Hat) JBoss Developer Studio, MyEclipse, Microsoft Visual Studio Code и Netbeans. Полный список поддерживаемых IDE и советы по установке даны на официальном сайте Lombok. .
Рискованно ли работать с Lombok?
Возможно, вас беспокоит, а что будет, если вы расставите аннотации Lombok по всей вашей базе кода. В самом деле, а что будет, если после этого вы решите избежать работы с Lombok? Возможно, в таком случае работа забуксует. Но на практике это не слишком большая проблема, поскольку в Lombok есть инструмент delombok. Как указано в официальной документации (где, правда, не покрыты все возможные IDE и случаи), этот инструмент действительно упрощает процесс разломбочивания вашего кода. Фактически, он обеспечивает автоматическую генерацию исходного кода Java, содержащего в байт-коде ровно те же возможности, которые внедрялись бы при помощи Lombok. Таким образом, ваша база кода, аннотированная Lombok, просто заменяется стандартной базой кода на Java, без Lombok. После этого весь ваш проект больше вообще не будет зависеть от Lombok. Соответственно, работа с Lombok не представляет никаких рисков для развития вашего проекта в будущем.
Заключение
В этой статье было рассмотрено, как использовать проект Lombok — библиотеку Java, которая автоматически подключается к вашему редактору и инструментам сборки, чтобы вам не приходилось писать скучного шаблонного повторяющегося кода, который так характерен для языка Java. Как показано в статье, Lombok помогает разработчику легко повысить производительность труда и не тратить времени на утомительную рутину. Научившись пользоваться самыми важными аннотациями, можно обойтись без написания тысяч строк кода, который не несет никакой реальной пользы для бизнес-логики вашего проекта. Кроме того, в любой момент можно легко отвязать ваш проект от использования проекта Lombok.