Lombok — проект по добавлению дополнительной функциональности в Java c помощью изменения исходного кода перед Java компиляцией.
По сути, проект Lombok позволяет избавиться от многословности Java в большинстве случаев и перестать писать огромные простыни кода из гетеров, сеттеров, equals, hashcode и toString (да их обычно генерит IDE, но читать и менять все равно приходится программисту), в результате Java становиться почти такой же краткой как Kotlin, Scala или C#.
Что особенно радует, Lombok очень прост и легок в добавлении к вашему проекту. Если вам, как и мне, нравится принцип KISS, то советую посмотреть на Lombok.
Так же рекомендую, посмотреть на другие статьи цикла, например последную шпаргалку по Java SE8
Добавление в проекты очень простое, достаточно добавить обычные зависимости:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
Так же можно (но не обязательно добавить плагин для работы статических компиляторов, если нужно анализировать код после компиляции Lombok), см. тут
Gradle
plugins {
id 'net.ltgt.apt' version '0.10'
}
dependencies {
compileOnly 'org.projectlombok:lombok:1.16.18'
apt "org.projectlombok:lombok:1.16.18"
}
Так же есть плагины для Idea, Eclipse и т.п. Если вы собираете Gradle или Maven, то собираться будет и без этих плагинов, но будут Idea/Eclipse возможно будут показывать ошибки при анализе кода.
Команды и аннотации:
Название | Описание | Пример Lombok |
Пример обычной Java |
---|---|---|---|
@NonNull |
обработка переменных, которые не должны получать null |
|
|
@Getter / |
легкое создание getter’ов и setter’ов |
|
|
@ToString |
определение аннотации перед классом, для реализации стандартного toString метода |
|
|
@EqualsAndHashCode |
легкое создание методов Equals и HashCode |
|
|
@NoArgsConstructor, |
создания пустого конструктора, конструктора включающего все final поля, либо конструктора включающего все возможные поля |
|
|
@Data |
генерация всех служебных методов, заменяет сразу команды @ToString, @EqualsAndHashCode, Getter, Setter, @RequiredArgsConstructor |
|
Много кода
|
@Value |
создание неизменяемых классов, аналог Data, но для неизменяемых классов |
|
Много кода
|
@Builder |
реализация паттерна bulder, Singular – используется для объектов в единственном экземпляре (добавления элемента в коллекции и т.п.) |
|
много кода
|
@SneakyThrows |
обертка проверяемых исключений | @SneakyThrows( UnsupportedEncodingException.class) public String utf8ToString(byte[] bytes) { return new String(bytes, «UTF-8»); } |
|
@Synchronized |
простое создание synchronized блоков |
|
Много кода
|
@Log |
добавление инницилизации логирования, так же позволяет выбрать вид логгера: @CommonsLog, @JBossLog, Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j |
|
|
Val | простое создание финальной переменной с выводом типа, то есть то самый val о котором спорили |
|
|
@Cleanup |
простое определение ресурсов, так чтобы они автоматически закрывались после окончания работы кода. (не так актуально при использовании try with resources ) |
|
|
Описание: обработка переменных,
которые не должны получать null
Код Lombok:
public Example(@NonNull P p) {
super("Hello");
this.name = p.getName();
}
Код обычной Java:public Example(@NonNull P p) {
super("Hello");
if (p == null) {
throw new NullPointerException("p");
}
this.name = p.getName();
}
Название: Getter /
Setter
Описание: легкое создание getter’ов и
setter’ов
Код Lombok:
@Getter
@Setter
private int age = 10;
Код обычной Java:private int age = 10;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
Название: @ToString
Описание: определение аннотации перед классом,
для реализации стандартного toString метода
Код Lombok:
@ToString(exclude="f")
public class Example
Код обычной Java:public class Example {
@Override
public String toString() {
return ...;
}
Название: @EqualsAndHashCode
Описание: легкое создание методов Equals и HashCode
Код Lombok:
@EqualsAndHashCode(
exclude={"id1", "id2"})
public class Example {
Код обычной Java:public class Example {
...
@Override
public boolean equals(Object o) {
...
}
@Override
public int hashCode() {
...
}
Название:
@NoArgsConstructor,
@RequiredArgsConstructor,
@AllArgsConstructor
Описание: создания пустого конструктора,
конструктора включающего все final поля,
либо конструктора включающего все возможные поля
Код Lombok:
@RequiredArgsConstructor(
staticName = "of"
)
@AllArgsConstructor(
access = AccessLevel.PROTECTED
)
public class E<T> {
Код обычной Java:public class E<T> {
private E(T description) {
...
}
public static <T>E<T> of(
T description
) {
return new E<T>(description);
}
Название: Data
Описание: генерация всех служебных методов,
заменяет сразу команды @ToString, @EqualsAndHashCode,
Getter, Setter, @RequiredArgsConstructor
Код Lombok:
@Data
public class Example {
private final String name;
private int age;
}
public class Example {
private final String name;
private int age;
public Example(
String name
) {
this.name = name;
}
public String getName() {
return this.name;
}
void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
@Override
public String toString() {
return ...;
}
@Override
public boolean equals(
Object o
) {
....
}
@Override
public int hashCode() {
...
}
Название: Value
Описание: создание неизменяемых классов,
аналог Data, но для неизменяемых классов
Код Lombok:
@Value
public class Example {
private final String name;
private int age;
}
Код обычной Java:
public class Example {
private final String name;
private final int age;
public Example(
String name, int age
) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
@Override
public String toString() {
return ...;
}
@Override
public boolean equals(
Object o
) {
....
}
@Override
public int hashCode() {
...
}
Название: Builder
Описание: реализация паттерна bulder,
Singular – используется для объектов в
единственном экземпляре (добавления элемента
в коллекции и т.п.)
Код Lombok:
@Builder
public class Example {
private String name;
private int age;
@Singular
private Set<String> occupations;
}
Код обычной Java:
public class Example {
private String name;
private int age;
private Set<String> occupations;
Example(
String name,
int age,
Set<String> occupations
) {
this.name = name;
this.age = age;
this.occupations = occupations;
}
public static ExampleBuilder builder() {
return new ExampleBuilder();
}
public static class ExampleBuilder {
private String name;
private int age;
private ArrayList<> occupations;
ExampleBuilder() {
}
public ExampleBuilder name(
String name
) {
this.name = name;
return this;
}
public ExampleBuilder age(
int age
) {
this.age = age;
return this;
}
public ExampleBuilder occupation(
String occupation
) {
if (this.occupations == null) {
this.occupations =
new ArrayList<String>();
}
this.occupations.add(occupation);
return this;
}
...
public Example build() {
Set<String> occupations = ...;
return new Example(name, age, occupations);
}
@java.lang.Override
public String toString() {
...
}
}
}
Название: @SneakyThrows
Описание: обертка проверяемых исключений
Код Lombok:
@SneakyThrows(
UnsupportedEncodingException.class)
public String utf8ToString(byte[] bytes) {
return new String(bytes, «UTF-8»);
}
Код Lombok:
public String utf8ToString(byte[] bytes) {
try {
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw Lombok.sneakyThrow(e);
}
}
Название: @Synchronized
Описание: простое создание synchronized блоков
Код Lombok:
private final Object readLock = new Object();
@Synchronized
public static void hello() {
...;
}
@Synchronized
public int answerToLife() {
...
}
@Synchronized("readLock")
public void foo() {
...
}
Код обычной Java:
private static final Object $LOCK = new Object[0];
private final Object $lock = new Object[0];
private final Object readLock = new Object();
public static void hello() {
synchronized($LOCK) {
...
}
}
public int answerToLife() {
synchronized($lock) {
...
}
}
public void foo() {
synchronized(readLock) {
...
}
}
Название: Log
Описание: добавление инницилизации логирования,
так же позволяет выбрать вид логгера: @CommonsLog,
@JBossLog, Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j
Код Lombok:
@Slf4j
public class Example {
public static void main(String... args) {
log.error("error");
}
Код обычной Java:public class Example {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExampleOther.class);
public static void main(String... args) {
log.error("error");
}
переменной с выводом типа,
то есть то самый val о котором
спорили
val map = new HashMap<Integer, String>();
for (val entry : map.entrySet()) {
...
}
final HashMap<Integer, String> map = new HashMap<Integer, String>();
...
for (final Map.Entry<Integer, String> entry : map.entrySet()) {
...
}
Название: @Cleanup
Описание: простое определение ресурсов,
так чтобы они автоматически закрывались
после окончания работы кода.
(не так актуально при использовании
try with resources )
Код Lombok:
@Cleanup InputStream in = new FileInputStream(args[0]);
@Cleanup OutputStream out = new FileOutputStream(args[1]);
...
Код обычной Java:InputStream in = new FileInputStream(args[0]);
try {
OutputStream out = new FileOutputStream(args[1]);
try {
...
} finally {
if (out != null) {
out.close();
}
}
} finally {
if (in != null) {
in.close();
}
}
Комментарии (33)
NimElennar
26.12.2017 17:29+1Беда начинается, когда требуется в одном проекте использовать и Lombok и AspectJ, они не очень дружат.
vedenin1980 Автор
26.12.2017 17:38А можете рассказать, что происходит? Я не сталкивался с такими проблемами, вроде AspectJ по идее работает уже с сгенеренными классами (или я ошибаюсь?), а сгенеренные классы Lombok не отличаются от обычных.
NimElennar
26.12.2017 21:22palesz.wordpress.com/2011/12/03/howto-maven-lombok-and-aspectj-together
1. javac compiles your .java files to .class files with lombok (generating methods, etc.)
2. aspectj regenerates your classes from the .java files without lombok
Но, к сожалению, предложенный по ссылке вариант у меня не сработал. И не только у меня, насколько можно понять из issue того же lombok — github.com/rzwitserloot/lombok/issues/995
igorperciuleac
26.12.2017 17:29+2Никогда не понимал людей кто юзает эту либу.
Для нового продукта можно выбрать лаконичные scala или koltin.
А в бородатый энтерпрайз вряд ли кто то станет это внедрять.
Нужно принимать язык таким как каким он есть.vedenin1980 Автор
26.12.2017 17:49-1Для нового продукта можно выбрать лаконичные scala или koltin.
Можно, но если вся команда привыкла писать на Java почему бы и нет. У scala многих пугает сложность, понятно, что можно писать как чуть лучшая Java, но если работать с чужим кодом можно ничего не понять. koltin пока не так распространен. Данная библиотека это компромис между новым языком и старым.
А в бородатый энтерпрайз вряд ли кто то станет это внедрять.
Кроме энтерпрайза есть просто старые проекты, которые по каким-то причинам использовали Java (скажем, микросервисы). С одной стороны переходит на новый язык сложно, с другой добавление библиотеки позволяет рефакторить старый код. Вообще, в Java энтерпрайз все больше переходит на Spring Boot и микросервисы, а это могут быть совсем не огромные проекты.
Нужно принимать язык таким как каким он есть.
Не согласен, так можно было вообще писать на Java 5 или Java 3. Язык вполне гибкая штука, в Java огромное количество открытых библиотек, которые тоже расширяют возможности. По такой логики их тоже не стоит использовать.igorperciuleac
26.12.2017 18:52+2Не согласен, так можно было вообще писать на Java 5 или Java 3. Язык вполне гибкая штука, в Java огромное количество открытых библиотек, которые тоже расширяют возможности. По такой логики их тоже не стоит использовать.
Вы передергиваете :) Речь ведь о языке в целом, а не конкретно какой-то её версии.
Генерация кода для DI вынужденная мера, тут я согласен.
В остальном, не западло прописать getter/setter потому что это Java, тут так принято.
Хотите нормальную аргументацию?
Для нормальной работы этого чудо модуля нужно ставить расширение для IntelliJ IDEA
Все эти генерации могут усложнить/замедлить скорость сборки проекта
Все эта магия вгонит в ступор первого же человека кто начнет разбираться в таком коде.
Ненужно пытаться нагнать всевдо-читаемость такими плагинами.
Если что, есть много других языков где ненужно писать getter/setter :)acmnu
26.12.2017 20:00Если что, есть много других языков где ненужно писать getter/setter :)
Эм… Много? Что-то как-то в голову даже 5 не приходит.
AstarothAst
27.12.2017 11:20Все эта магия вгонит в ступор первого же человека кто начнет разбираться в таком коде.
Спринговый @Compontent тоже вгонит, так что ж теперь, Спрингом не пользоваться?igorperciuleac
27.12.2017 11:34Аннотация @Compontent в отличии от аннотаций Lombok не дописывает код вместо вас, вот в чем разница!
AstarothAst
27.12.2017 11:43Для человека, который этого не знает и та и другая аннотация выглядят магически и требуют времени для постижения того, что это такое и как работает, так что с этой точки зрения разница не велика. При этом, замечу, квикдок по аннотации Data первым делом сообщает: "Generates getters for all fields", плюс дает ссылку на сайт ломбока, что сильно помогает делу.
igorperciuleac
27.12.2017 11:54Я не знаю как по другому еще донести до вас суть. Вы меня не хотите слышать, эта ветка зашла в тупик.
AstarothAst
27.12.2017 11:59Суть вами сказанного, собственно, ясна. Просто есть иной взгляд на вещи, вот и все.
igorperciuleac
27.12.2017 12:07Для меня лично не приемлем тот факт что без специального плагина для IDE с этим самым Lombok невозможно нормально работать. Это главная причина того, почему я не буду использовать это в своих проектах.
AstarothAst
27.12.2017 13:14Со Спрингом, кстати, тоже невозможно нормально работать в комьюнити эдишн версии, но многие работают, и при этом говорят, что им норм :)
Sultansoy
26.12.2017 20:35-1Ну мне лично не нравится синтаксис котлина и скали, а джава с си подобным синтаксисом очень даже ничего. В принципе сгенерировать через alt insert в идее эти геттеры сеттеры тоже не сложно, но ломбок вообще шикарен.
AstarothAst
27.12.2017 11:17+1Есть объект класса, нужно вернуть его часть в виде json — я создаю отдельную обертку, которая в конструктор принимает этот объект, и берет из него все нужное, после чего объект обертки прогоняется через jackson, и на выходе получаем json. Jackson, как известно, пользуется для конвертации либо публичными полями, либо геттерами, поэтому одна единственная аннотация Data над классом-оберткой быстро и не заметно экономит мне время. Еще одна аннотация @Slf4j позволяет мне работать с логгером даже не думая о нем. В общем не очень понимаю ваше не понимание, о каком «внедрении в бородатый энтерпрайз» вообще идет речь? Это же библиотека, а не фреймворк — если удобно, то используем, если не удобно, то не используем.
igorperciuleac
27.12.2017 11:31+1Немного необычный подход с оберткой над объектом для генерации json.
Обычно делаются статические методы в хелпер-классе, например EntityHelper.toJson(myEntity)
В вашем случае идет совершенно ненужное создание +1 объекта обертки к каждой операции конвертации объекта в Json
Не буду спорить что ваш подход выглядит короче, круче :)AstarothAst
27.12.2017 11:51Ваш подход не подойдет в случае если нет однозначного соответствия Класс<->json. То есть в одном случае нужен json состоящий из некоторых полей класса А, в другом — из всех полей, а в третьем случае вообще должен быть композицией из A + B + C. Плюс мне нравится иметь возможность на вопрос «почему фронту приходит ЭТО вместо ЭТОГО?» пойти и подправить одну конкретную обертку гарантированно не затронув ничего по соседству. Я такие обертки вообще частенько создаю, как inner классы прямо по месту преобразования результата.
igorperciuleac
27.12.2017 12:03Нужно конечно видеть код, что бы подсказать альтернативный путь для решения этой задачи без дополнительных объектов-оберток.
Возможно что ваш подход хорошо подходит именно в вашем случае. Тут я спорить не буду.AstarothAst
27.12.2017 13:15А в чем проблема дополнительных оберток, кстати говоря? Формально понятно, лишняя сущность, но практически она маленькая, локальная и вопросов не вызывает. Стоит ли с ней бороться?
igorperciuleac
27.12.2017 13:27В вашем случае, на сколько я понял, это обертка возвращается вашим контроллером, по сути ничего, кроме эстетического момента меня не смущает.
Если же вы проектируете api какой-либо библиотеки, которую будут использовать сторонние разработчики, то тут я бы воздержался от таких конструкций. Разработчик может вызывать такой код в различных циклах миллионы раз, зачем провоцировать GC на лишнюю работу за зря?AstarothAst
27.12.2017 13:39Не совсем так. У меня выделена отдельная сущность «экшен», который с одной стороны может вызываться на выполнение фронтэндом, путем отправления по вебсокету специального сообщения, а с другой стороны в него интегрирован доступ к ядерным сервисам, которые обеспечивают работу самого приложения, и знать не знают ни о какой фронтовой части. То есть реализуя экшен (java код) на получение, к примеру, данных о работнике, которые на ядерном уровне распределены по нескольким компонентам, мы делам примерно так:
UserProfile userProfile = userService.getProfile(id);
EmployeeProfile employeeProfile = employeeService.getProfile(id);
после чего компонуем эти два профиля в одну entity, состоящую, скажем, из ФИО (взятого из userProfile) и количества рабочих часов в месяц (из employeeProfile), которую экшен и возвращает для дальнейшего превращения в json, и возврата фронтэнду. При таком подходе результирующий entity даже в виде inner класса в классе экшена смотрится довольно уместно.
А насчет библиотеки это да, тут согласен.
3draven
26.12.2017 23:31Пользую ломбок давненько, в основном Getter/Setter/ToString/Equal…
По моему удобно и Entity сокращаются в размерах в разы.
Только следить за циклическими зависимостями в ManyToMany и прочих подобных штуках надо путем использования exclude ну и с Equal… осторожно, в остальном просто супер.
DarkByte2015
27.12.2017 13:24Выглядит круто, только вот один вопрос очень интересует: если физически в коде например геттеров/сеттеров нет (добавятся аннотацией), а ты их будешь юзать, не будет ли IDE их подчеркивать все время как ошибку? Она то не знает что они добавятся во время компиляции… И вообще как с ними автокомплит будет работать?
vedenin1980 Автор
27.12.2017 13:25Для всех IDE есть специальные плагины, при которых все работает нормально.
akass
Планируется ли обзор DI решений (Guice, Dagger, HK2)?
vedenin1980 Автор
Да, уже есть наполовину написанная статья по Guice, Dagger, Spring, с HK2 не сталкивался, посмотрю.