Перейти к основному содержимому

Ломбок с использованием аннотаций @With

· 5 мин. чтения

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 .