Статьи

Declutter ваши POJO с Lombok

У меня есть отношения любовь / ненависть с Java. С одной стороны, это зрелый язык программирования с разнообразным количеством фреймворков и библиотек, которые делают разработку относительно простой. С другой стороны, это очень многословно и требует написания огромного количества стандартного кода для общих задач. Ситуация улучшилась с введением лямбда-выражений и потоков в Java 8, но в некоторых областях она все еще не соответствует требованиям, таким как написание простых старых объектов Java POJO . В этой статье я покажу вам, как переписать POJO в несколько строк кода с помощью Lombok .

Подробные POJO

Взгляните на этот обычный класс POJO с тремя полями: name , surname и age . Он имеет элементы, которые являются общими для классов POJO: методы получения, установки, equals , hashCode и метод toString .

 public class User { private String name; private String surname; private int age; public User(String name, String surname, int age) { this.name = name; this.surname = surname; this.age = age; } 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; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return age == user.age && Objects.equals(name, user.name) && Objects.equals(surname, user.surname); } @Override public int hashCode() { return Objects.hash(name, surname, age); } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", surname='" + surname + '\'' + ", age=" + age + '}'; } } 

Нам потребовалось 59 строк кода, чтобы реализовать этот простой класс с тремя полями. Конечно, IDE может сгенерировать его для нас, но помните, что код читается гораздо чаще, чем пишется. Итак, что еще хуже, чем просто количество строк, которое требуется для представления этой простой концепции, заключается в том, что код не может четко передать намерение реализации. Сколько времени вам понадобится, чтобы ответить на эти вопросы:

  • Какие поля не имеют геттеров / сеттеров?
  • Какие поля не используются в методах equals / hashCode / toString ?
  • Какие поля имеют нестандартные геттеры / сеттеры?
  • Какие геттеры / сеттеры имеют непубличные модификаторы?

Получить эти ответы не невозможно, но это намного сложнее, чем должно быть. Отношение сигнал / шум слишком низкое!

Ситуация становится еще хуже, если нам нужно реализовать шаблон компоновщика . Помимо всего кода POJO, нам нужно поддерживать подобный шаблон для еще одного класса — примерно вдвое больше пограничного бесполезного кода. Как будто это не так уж плохо, когда нам нужно добавить новое поле, мы должны обновить как конструктор, так и класс POJO в нескольких местах. Это позволяет легко что-то пропустить и внести ошибку.

Представляем Ломбок

С удовольствием есть лучший способ. Lombok — это библиотека, которая позволяет нам определять классы POJO, используя ряд простых, но мощных аннотаций. Эти аннотации указывают, должно ли определенное поле иметь метод getter / setter, должно ли оно участвовать в методах equals / hashCode / toString и т. Д. Lombok существует уже несколько лет и используется во многих коммерческих проектах и ​​проектах с открытым исходным кодом.

Итак, давайте добавим Lombok в наш проект. Это очень просто, просто добавьте следующую зависимость в ваш файл pom.xml :

 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.10</version> <scope>provided</scope> </dependency> при <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.10</version> <scope>provided</scope> </dependency> , <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.10</version> <scope>provided</scope> </dependency> 

После этого ваш код Lombok будет скомпилирован без проблем, но если вы используете IDE, у вас будут проблемы. Поскольку аннотации Lombok генерируют новый код (подробнее о том, как работает Lombok, см. Ниже), среда IDE должна знать, какие методы неявно добавляются какой аннотацией. К счастью, есть несколько плагинов для разных IDE, так что вы можете выбрать свой любимый .

Если вы используете Intellij IDEA, вам необходимо скачать сторонний плагин для поддержки Lombok. Если вы используете Eclipse, вам нужно скачать lombok.jar и просто выполнить его. Будет установлен плагин Eclipse. Для NetBeans вам нужен тот же JAR и включить обработку аннотаций .

Теперь мы готовы улучшить наш код с помощью Lombok.

Использование Lombok

Lombok предоставляет несколько основных аннотаций, чтобы определить, должно ли определенное поле иметь метод доступа или его следует использовать в методах equals / hashCode / toString :

 @EqualsAndHashCode @ToString @AllArgsConstructor public class User { @Setter @Getter private String name; @Setter @Getter private String surname; @Setter @Getter private int age; } 

Как видите, у нас есть пять основных аннотаций. @EqualsAndHashCode и @ToString инструктируют Lombok генерировать методы equals , hashCode, и toString которые будут использовать все поля в классе. @AllArgsConstructor создаст конструктор, который имеет столько аргументов, сколько имеется полей в классе. @Getter / @Setter применяются к каждому полю и создают геттеры и сеттеры.

Теперь мы можем использовать этот класс как обычный класс POJO:

 User user = new User("John", "Doe", 32); user.setAge(30); user.equals(new User("John", "Doe", 30)); // true 

Если вы не хотите, чтобы аннотация использовала все поля, вы можете предоставить необязательный параметр, который указывает, какие поля следует использовать для создания метода. Поэтому, если мы не хотим использовать поле age в методе toString вы можете сделать это следующим образом:

 @EqualsAndHashCode @ToString(of = {"name", "surname"}) @AllArgsConstructor public class User { @Getter @Setter private String name; @Getter @Setter private String surname; @Getter @Setter private int age; } 

Я бы сказал, что этот код уже намного лучше, чем тот, с которого мы начали, но он все еще не выглядит кратким. Как насчет этих повторяющихся аннотаций @Getter / @Setter ? Удобно использовать их таким образом, если вам нужно представить только пару полей в вашем классе, но если вам нужно сгенерировать методы доступа для всех ваших полей, это становится раздражающим.

Чтобы сделать этот случай еще более лаконичным, Lombok позволяет использовать эти аннотации на уровне класса. Если аннотации @Getter / @Setter используются на уровне класса, Lombok сгенерирует методы получения и установки для всех полей в классе.

 @EqualsAndHashCode @ToString @AllArgsConstructor @Getter @Setter public class User { private String name; private String surname; private int age; } 

Это не меняет поведение класса, но теперь код стал еще более лаконичным.

Реализация Builder

В начале поста я утверждал, что строители добавляют еще один уровень шаблона. Ломбок превращает упражнение написания строителя в однострочник. Для этого просто добавьте аннотацию @Builder поверх класса, и у вас будет в наличии конструктор с плавным интерфейсом:

 @EqualsAndHashCode @ToString @AllArgsConstructor @Getter @Setter @Builder public class User { private String name; private String surname; private int age; } 

Теперь мы можем создать экземпляр нашего класса, используя конструктор:

 User user = User.builder() .name("John") .surname("Doe") .age(32) .build(); 

Редукционный котел Ломбок

Код результата более читабелен, но как насчет этого повторяющегося набора аннотаций? У большинства POJO есть все эти методы, и все еще громоздко использовать одни и те же аннотации снова и снова. Lombok предоставляет две аннотации, которые могут сделать это еще проще:

  • @Data — аннотация, которая заменяет @Getter , @Setter , @EqualsAndHashCode , @ToString и @RequiredArgsConstructor
  • @Value — то же, что и раньше, но генерирует неизменный класс без установщиков

@Data и @Value получили свои названия от так называемых классов данных и классов значений . Классы данных — это изменчивые классы, которые предоставляют свои поля через методы получения и установки. С другой стороны, классы значений являются неизменяемыми и обычно не имеют никакой логики, кроме методов equals , hashCode и toString .

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

 @Data @Builder public class User { private String name; private String surname; private int age; } 

Этот пример не только намного короче, но и гораздо лучше передает смысл этого кода. Понятно, что у этого класса есть три поля, у всех есть геттеры и сеттеры, все они используются в методах hashCode / equals / toString , и для этого есть класс построителя.

Определение пользовательских методов с помощью Lombok

Вы можете задать один вопрос: что если вам нужно определить пользовательский метод получения или установки? Что если вам нужно добавить пользовательские границы, проверяющие значение age ?

В этом случае Lombok ведет себя довольно просто: если метод, который собирается сгенерировать Lomobok, уже находится в классе, он не генерирует новый. Следовательно, он никогда не переопределяет ваши методы и только добавляет новые методы в класс.

Как работает Ломбок

Так как же работает Ломбок? Оказывается, он опирается на один официальный стандарт Java и один огромный хак.

Но прежде чем мы придем к этому, мы должны немного лучше понять компилятор Java. Во время компиляции он выполняет тяжелую работу и преобразует исходный код в древовидную структуру, называемую абстрактным синтаксическим деревом (AST). AST содержит всю информацию об исходном коде, такую ​​как классы, методы и операторы программы. На этом компилятор выполняет различные проверки и преобразования, а затем использует результирующий AST для генерации окончательных инструкций байт-кода.

Основываясь на этом, Java 6 представила API Pluggable Annotation Processing (стандартизированный JSR 269 ), который позволяет библиотекам Java выполнять пользовательский код во время компиляции. Первоначальное намерение стандарта заключается в том, что библиотеки будут проверять только AST, особенно аннотации, и использовать их для реализации пользовательских проверок кода или создания новых исходных файлов.

Но создатели Lombok выяснили, что они могут использовать эту функцию по-другому. Используя непубличный API, им удалось изменить AST, предоставляемые компилятором, для добавления новых методов, полей и даже классов на этапе компиляции. Затем компилятор использует этот модифицированный AST для генерации байт-кода для сгенерированных Lombok методов и полей, как если бы они были написаны разработчиком.

Delombok

Не все зависимости, добавленные в проект, остаются там навсегда, и иногда нам нужно избавиться от них, чтобы двигаться вперед. К счастью, Lombok легко позволяет нам возвращать все классы «lomboked» в обычную Java, используя процесс под названием delombok . Для этого вам нужно вызвать команду delombok из lombok.jar :

 java -jar lombok.jar delombok src -d src-delomboked 

Это генерирует исходный код для всех методов и классов, которые Lombok обычно вращает во время компиляции, и записывает его в указанную папку.

Вы также можете добавить задачу delombok в ваш скрипт Ant:

 <mkdir dir="build/src-delomboked" /> <delombok verbose="true" encoding="UTF-8" to="build/src-delomboked" from="src"> <format value="suppressWarnings:skip" /> </delombok> 

Последний метод удобен, если вы хотите сохранить свой код как есть, но вам нужно сгенерировать JavaDoc для кода с аннотациями Lombok.

Резюме

Как вы можете видеть, Lombok может значительно ослабить ваш Java-код. Он использует краткие аннотации для генерации повторяющихся и подробных классов и методов построителя, таких как геттеры, сеттеры и конструкторы. Это может легко помочь вам избавиться от тысяч строк стандартного кода, даже в проекте среднего размера. Lombok также позволяет вам сделать ваш код более выразительным, лаконичным и может помочь избежать некоторых ошибок. Наконец, он также достаточно универсален для использования в самых разных проектах — от консольных приложений до веб-приложений.