1. Введение
Lombok — это библиотека, которая помогает нам значительно сократить шаблонный код при написании Java-приложений.
В этом руководстве мы увидим, как с помощью этой библиотеки можно создавать копии неизменяемых объектов с изменениями только одного свойства.
2. Использование
При работе с неизменяемыми объектами, которые по своей конструкции не допускают установки, нам может понадобиться объект, аналогичный текущему, но отличающийся только одним свойством. Этого можно добиться с помощью аннотации Lombok @With
:
public class User {
private final String username;
private final String emailAddress;
@With
private final boolean isAuthenticated;
//getters, constructors
}
Приведенная выше аннотация генерирует следующее под капотом:
public class User {
private final String username;
private final String emailAddress;
private final boolean isAuthenticated;
//getters, constructors
public User withAuthenticated(boolean isAuthenticated) {
return this.isAuthenticated == isAuthenticated ? this : new User(this.username, this.emailAddress, isAuthenticated);
}
}
Затем мы можем использовать сгенерированный выше метод для создания измененных копий исходного объекта:
User immutableUser = new User("testuser", "test@mail.com", false);
User authenticatedUser = immutableUser.withAuthenticated(true);
assertNotSame(immutableUser, authenticatedUser);
assertFalse(immutableUser.isAuthenticated());
assertTrue(authenticatedUser.isAuthenticated());
Кроме того, у нас есть возможность аннотировать весь класс, который будет генерировать методы withX()
для всех свойств .
3. Требования
Чтобы правильно использовать аннотацию @With ,
нам нужно предоставить конструктор со всеми аргументами . Как видно из приведенного выше примера, сгенерированный метод требует этого для создания клона исходного объекта.
Мы можем использовать собственную аннотацию Lombok @AllArgsConstructor
или @Value
, чтобы удовлетворить это требование. Кроме того, мы также можем вручную предоставить этот конструктор, гарантируя, что порядок нестатических свойств в классе соответствует порядку конструктора.
Мы должны помнить, что аннотация @With
ничего не делает, если используется для статических полей . Это связано с тем, что статические свойства не считаются частью состояния объекта. Кроме того, Lombok пропускает генерацию метода для полей, начинающихся со знака $
.
4. Расширенное использование
Давайте рассмотрим некоторые расширенные сценарии использования этой аннотации.
4.1. Абстрактные классы
Мы можем использовать аннотацию @With
для поля абстрактного класса:
public abstract class Device {
private final String serial;
@With
private final boolean isInspected;
//getters, constructor
}
Однако нам нужно будет предоставить реализацию для сгенерированного метода withInspected()
. Это потому, что Ломбок не будет знать о конкретных реализациях нашего абстрактного класса для создания его клонов:
public class KioskDevice extends Device {
@Override
public Device withInspected(boolean isInspected) {
return new KioskDevice(getSerial(), isInspected);
}
//getters, constructor
}
4.2. Соглашения об именах
Как мы определили выше, Lombok пропустит поля, начинающиеся со знака $ .
Однако, если поле начинается с символа, оно указывается в заголовке и, наконец, с
префиксом к сгенерированному методу.
В качестве альтернативы, если поле начинается с подчеркивания, то with
просто добавляется префикс к сгенерированному методу:
public class Holder {
@With
private String variableA;
@With
private String _variableB;
@With
private String $variableC;
//getters, constructor excluding $variableC
}
Согласно приведенному выше коду мы видим, что только для первых двух переменных `будут сгенерированы методы
withX() для них:`
Holder value = new Holder("a", "b");
Holder valueModifiedA = value.withVariableA("mod-a");
Holder valueModifiedB = value.with_variableB("mod-b");
// Holder valueModifiedC = value.with$VariableC("mod-c"); not possible
4.3. Исключения для генерации методов
Мы должны помнить, что в дополнение к полям, начинающимся со знака $ ,
Lombok не будет генерировать метод withX()
, если он уже существует в нашем классе :
public class Stock {
@With
private String sku;
@With
private int stockCount;
//prevents another withSku() method from being generated
public Stock withSku(String sku) {
return new Stock("mod-" + sku, stockCount);
}
//constructor
}
В приведенном выше сценарии новый метод withSku()
не будет создан.
Кроме того, Lombok пропускает создание метода в следующем сценарии :
public class Stock {
@With
private String sku;
private int stockCount;
//also prevents another withSku() method from being generated
public Stock withSKU(String... sku) {
return sku == null || sku.length == 0 ?
new Stock("unknown", stockCount) :
new Stock("mod-" + sku[0], stockCount);
}
//constructor
}
Мы можем заметить другое название метода withSKU()
выше.
По сути, Lombok пропустит генерацию метода, если:
- Тот же метод существует в качестве сгенерированного имени метода (без учета регистра)
- Существующий метод имеет то же количество аргументов, что и сгенерированный метод (включая var-args).
4.4. Нулевые проверки сгенерированных методов
Подобно другим аннотациям Lombok, мы можем включить проверки на null в методы, сгенерированные с помощью аннотации
@With
:
@With
@AllArgsConstructor
public class ImprovedUser {
@NonNull
private final String username;
@NonNull
private final String emailAddress;
}
Lombok сгенерирует для нас следующий код вместе с необходимыми проверками на null :
public ImprovedUser withUsername(@NonNull String username) {
if (username == null) {
throw new NullPointerException("username is marked non-null but is null");
} else {
return this.username == username ? this : new ImprovedUser(username, this.emailAddress);
}
}
public ImprovedUser withEmailAddress(@NonNull String emailAddress) {
if (emailAddress == null) {
throw new NullPointerException("emailAddress is marked non-null but is null");
} else {
return this.emailAddress == emailAddress ? this : new ImprovedUser(this.username, emailAddress);
}
}
5. Вывод
В этой статье мы увидели, как использовать аннотации Lombok @With
для создания клонов определенного объекта с изменением одного поля.
Мы также узнали, как и когда на самом деле работает генерация этого метода, а также как дополнить его дополнительными проверками, такими как проверки на null
.
Как всегда, примеры кода доступны на GitHub .