Статьи

Lombok, AutoValue и Immutables, или Как писать меньше и лучше возвращается код

В предыдущем посте о библиотеке Lombok я описал библиотеку, которая помогает работать с шаблонным кодом на Java (и да, я знаю, что эти проблемы уже решены в Kotlin , но это реальная жизнь, и мы не можем просто сидеть и переписать каждый существующий проект, как только появится более новый или более простой язык). Но, как и многие вещи в жизни, проект Lombok имеет свои альтернативы. Давайте дадим им шанс.

Образцы кода для этой статьи можно найти здесь и здесь .

Google AutoValue

Это действительно альтернатива Lombok — потому что вы не можете использовать оба сразу. Или, по крайней мере, оказывается, что у вас возникнут трудности при использовании обоих в одном проекте с IntelliJ IDEA , который является IDE для многих и вашей по-настоящему — потому что две библиотеки по-разному обрабатывают аннотации. Таким образом, ни один из них не может жить, пока выживает другой, что примерно так прозвучало в пророчестве о Гарри Поттере и Волан-де-Морте .

Итак, мы уже знаем, как класс Person выглядел с аннотациями Lombok :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@Builder(toBuilder = true)
@ToString
@EqualsAndHashCode
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Person {
    @NonNull
    @Getter
    private final String lastName;
    @NonNull
    @Getter
    private final String firstName;
    @NonNull
    @Getter
    private final Integer age;
}

Если мы создадим новый проект и сделаем так, чтобы он использовал autovalue, как описано здесь , мы можем имитировать почти такую ​​же модель с помощью AutoValue Builders .

Теперь посмотрим, как выглядит модель AutoValue :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package autovalue.model;
 
import com.google.auto.value.AutoValue;
 
@AutoValue
public abstract class Person {
    public abstract String lastName();
 
    public abstract String firstName();
 
    public abstract Integer age();
 
    public static Person create(String lastName, String firstName, Integer age) {
        return builder().lastName(lastName).firstName(firstName).age(age).build();
    }
 
    public static Builder builder() {
        return new AutoValue_Person.Builder();
    }
 
    @AutoValue.Builder
    public abstract static class Builder {
        public abstract Builder lastName(String lastName);
        public abstract Builder firstName(String firstName);
        public abstract Builder age(Integer age);
 
        public abstract Person build();
    }
}

То, что вы можете видеть, это определенно больше кода .

В то время как Lombok генерирует компоновщик с одной аннотацией, AutoValue заставит вас создать свой собственный код компоновщика — но не все. По сути, вы определяете свои интерфейсы, а реализация остается за кодом, сгенерированным AutoValue , вам не нужно фактически реализовывать код, который находится в методах получения и установки. Даже если мы согласимся с тем, что интерфейсы получения AutoValue не будут занимать намного больше времени или пространства, чем определения полей Lombok , для некоторых людей написание кода построителя AutoValue может все еще быть хлопотным и неприятным.

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

Экземпляр создается так же, как и в Lombok .

1
2
3
4
5
final Person anna = Person.builder()
       .age(31)
       .firstName("Anna")
       .lastName("Smith")
       .build();

Все наши тесты выполняются с минимальными изменениями кода, в основном потому, что AutoValue не имеет способа преобразовать экземпляр в конструктор (или, по крайней мере, я не мог его легко найти), поэтому копирование — это просто вызов статического метода фабрики:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package autovalue.model;
 
import org.junit.Test;
 
import static org.assertj.core.api.Java6Assertions.assertThat;
 
public class PersonTest {
    private static Person JOHN = Person.builder()
            .firstName("John")
            .lastName("Doe")
            .age(30)
            .build();
    private static Person JANE = Person.builder()
            .firstName("Jane")
            .lastName("Doe")
            .age(30)
            .build();
 
    @Test
    public void testEquals() throws Exception {
        Person JOHN_COPY = Person.create(JOHN.lastName(), JOHN.firstName(), JOHN.age());
        assertThat(JOHN_COPY).isEqualTo(JOHN);
    }
 
    @Test
    public void testNotEquals() throws Exception {
        assertThat(JANE).isNotEqualTo(JOHN);
    }
 
    @Test
    public void testHashCode() throws Exception {
        Person JOHN_COPY = Person.create(JOHN.lastName(), JOHN.firstName(), JOHN.age());
        assertThat(JOHN_COPY.hashCode()).isEqualTo(JOHN.hashCode());
    }
 
    @Test
    public void testHashCodeNotEquals() throws Exception {
        Person JOHN_COPY = Person.create(JOHN.lastName(), JOHN.firstName(), JOHN.age());
        assertThat(JOHN_COPY.hashCode()).isNotEqualTo(JANE.hashCode());
    }
 
    @Test
    public void testToString() throws Exception {
        String jane = JANE.toString();
 
        assertThat(jane).contains(JANE.lastName());
        assertThat(jane).contains(JANE.firstName());
        assertThat(jane).contains("" + JANE.age());
        assertThat(jane).doesNotContain(JOHN.firstName());
    }
 
}

Другие отличия, которые сразу очевидны:

Почему вы должны использовать AutoValue ? Создатели AutoValue позаботились о том, чтобы описать здесь достижения библиотеки и даже создать целую презентацию об этом .

Библиотека неизменных

Библиотека также использует процессоры аннотаций Java для создания простых, безопасных и согласованных объектов значений. Ну так же, как и предыдущие два. Что еще нового? Давайте посмотрим.

Простейший класс значений будет выглядеть следующим образом.

01
02
03
04
05
06
07
08
09
10
package immutables.model;
 
import org.immutables.value.Value;
 
@Value.Immutable
public abstract class Person {
    public abstract String lastName();
    public abstract String firstName();
    public abstract Integer age();
}

Таким образом, существует тот же принцип наличия абстрактных классов, который реализован только в сгенерированном коде. Для этого вам нужно включить процессоры аннотаций IDE , так же, как вы делаете это для Lombok (но не для AutoValue , как там, где это делается плагином Gradle).

Как выглядит создание объекта?

1
2
3
4
5
6
final Person anna = ImmutablePerson.builder()
        .age(31)
        .firstName("Anna")
        .lastName("Smith")
        .build();
System.out.println(anna);

Наиболее очевидные различия на первый взгляд:

  • Мы не объявляем методы построения.
  • Статические методы компоновщика / фабрики создаются не в нашем собственном классе, а в сгенерированном.
  • Как и AutoValue, нет способа генерировать сеттеры для класса, только для компоновщика.
  • Сгенерированный класс также автоматически добавляет с -ers, то есть методами экземпляра, которые позволяют создать копию экземпляра, изменив одно свойство:
1
2
3
4
5
6
7
8
9
final ImmutablePerson anna = ImmutablePerson.builder()
        .age(31)
        .firstName("Anna")
        .lastName("Smith")
        .build();
System.out.println(anna);
 
final ImmutablePerson annaTheSecond = anna.withAge(23).withLastName("Smurf");
System.out.println(annaTheSecond);
  • У построителя есть автоматически добавленный метод from () , который позволяет создать точную копию экземпляра, а также сгенерированный статический метод copyOf () для сгенерированного класса:
1
2
3
Person JOHN_COPY = ImmutablePerson.builder().from(JOHN).build();
// OR
Person JOHN_COPY = ImmutablePerson.copyOf(JOHN);

И снова наш тест выполняется с минимальными изменениями, которые в основном касаются того, как мы копируем экземпляры:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package immutables.model;
 
import org.junit.Test;
 
import static org.assertj.core.api.Assertions.assertThat;
 
 
public class PersonTest {
    private static Person JOHN = ImmutablePerson.builder()
            .firstName("John")
            .lastName("Doe")
            .age(30)
            .build();
    private static Person JANE = ImmutablePerson.builder()
            .firstName("Jane")
            .lastName("Doe")
            .age(30)
            .build();
 
    @Test
    public void testEquals() throws Exception {
        //ImmutablePerson JOHN_COPY = ImmutablePerson.builder().from(JOHN).build();
        Person JOHN_COPY = ImmutablePerson.copyOf(JOHN);
        assertThat(JOHN_COPY).isEqualTo(JOHN);
    }
 
    @Test
    public void testNotEquals() throws Exception {
        assertThat(JANE).isNotEqualTo(JOHN);
    }
 
    @Test
    public void testHashCode() throws Exception {
        Person JOHN_COPY = ImmutablePerson.copyOf(JOHN);
        assertThat(JOHN_COPY.hashCode()).isEqualTo(JOHN.hashCode());
    }
 
    @Test
    public void testHashCodeNotEquals() throws Exception {
        Person JOHN_COPY = ImmutablePerson.copyOf(JOHN);
        assertThat(JOHN_COPY.hashCode()).isNotEqualTo(JANE.hashCode());
    }
 
    @Test
    public void testToString() throws Exception {
        String jane = JANE.toString();
 
        assertThat(jane).contains(JANE.firstName());
        assertThat(jane).contains(JANE.lastName());
        assertThat(jane).contains("" + JANE.age());
        assertThat(jane).doesNotContain(JOHN.firstName());
    }
 
}

О библиотеке Immutables можно сказать гораздо больше, так что здесь есть довольно большое руководство . Здесь, в этой статье, мы только немного поцарапали поверхность. Например, есть намного больше деталей о сериализации JSON с Immitables и настройках стиля (префиксы методов, имена сборщиков и т. Д.) И даже о генерации репозитория для Mongo, чтобы документы можно было рассматривать как неизменяемые . Но это гораздо больше, чем я хочу затронуть в этой простой статье.

Вывод: одна из проблем неиссякаемого, но все же языка Java — это многословность и шаблонный код. Но есть множество инструментов, чтобы справиться с этим, и можно выбрать библиотеку, которая подходит лучше всего, вместо того, чтобы кодировать путем копирования-вставки или пытаться написать свой собственный генератор кода.

Используйте их хорошо.

Используйте это хорошо.

Опубликовано на Java Code Geeks с разрешения Марины Чернявской, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Lombok, AutoValue и Immutables, или Как писать меньше и лучше возвращать код

Мнения, высказанные участниками Java Code Geeks, являются их собственными.