Статьи

AutoValue: сгенерированные классы неизменных значений

Размещенный в Google GitHub проект AutoValue интересен по нескольким причинам. Проект не только облегчает написание меньшего количества Java-кода для « объектов-значений », но также обеспечивает концептуально простую демонстрацию практического применения обработки аннотаций Java . Проект auto / value предоставлен сотрудниками Google Кевином Бурриллионом и Эамоном Макманусом и имеет лицензию Apache версии 2 .

Руководство пользователя AutoValue является кратким и конкретным, и эта краткость и простота отражают сам проект. Руководство пользователя содержит простые примеры использования AutoValue, обсуждает, почему AutoValue является желательным , краткие ответы на распространенные вопросы в разделе « Как сделать…» , а также предлагает некоторые рекомендации по использованию AutoValue .

Следующий листинг кода содержит простой класс, который я написал от руки. Этот класс был написан с учетом AutoValue.

Person.java

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
package dustin.examples.autovalue;
 
import com.google.auto.value.AutoValue;
 
/**
 * Represents an individual as part of demonstration of
 * GitHub-hosted project google/auto/value
 */
@AutoValue  // concrete extension will be generated by AutoValue
abstract class Person
{
   /**
    * Create instance of Person.
    *
    * @param lastName Last name of person.
    * @param firstName First name of person.
    * @param birthYear Birth year of person.
    * @return Instance of Person.
    */
   static Person create(String lastName, String firstName, long birthYear)
   {
      return new AutoValue_Person(lastName, firstName, birthYear);
   }
 
   /**
    * Provide Person's last name.
    *
    * @return Last name of person.
    */
   abstract String lastName();
 
   /**
    * Provide Person's first name.
    *
    * @return First name of person.
    */
   abstract String firstName();
 
   /**
    * Provide Person's birth year.
    *
    * @return Person's birth year.
    */
   abstract long birthYear();
}

При использовании AutoValue для генерации полноценных «классов значений» просто предоставляется абстрактный класс (интерфейсы намеренно не поддерживаются ), чтобы AutoValue генерировала соответствующее конкретное расширение. Этот abstract класс должен быть аннотирован аннотацией @AutoValue , должен предоставлять static метод, который предоставляет экземпляр класса значения, и должен предоставлять abstract методы доступа как из public и из области действия пакета, которые подразумевают поддерживаемые поля класса значения.

В приведенном выше листинге кода метод создания статического экземпляра создает экземпляр объекта AutoValue_Person , но такой класс AutoValue_Person определен. Этот класс — вместо этого имя сгенерированного класса AutoValue, который будет сгенерирован, когда обработка аннотации AutoValue выполняется в рамках компиляции javac Person.java . Из этого мы можем видеть соглашение об именовании сгенерированных AutoValue классов: AutoValue_ добавляется к имени исходного класса для формирования имени сгенерированного класса.

Когда Person.java компилируется с обработкой аннотаций AutoValue, применяемой как часть процесса компиляции, сгенерированный класс записывается. В моем случае (с использованием AutoValue 1.2 / auto-value-1.2.jar ) был сгенерирован следующий код:

AutoValue_Person.java: генерируется 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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package dustin.examples.autovalue;
 
import javax.annotation.Generated;
 
@Generated("com.google.auto.value.processor.AutoValueProcessor")
 final class AutoValue_Person extends Person {
 
  private final String lastName;
  private final String firstName;
  private final long birthYear;
 
  AutoValue_Person(
      String lastName,
      String firstName,
      long birthYear) {
    if (lastName == null) {
      throw new NullPointerException("Null lastName");
    }
    this.lastName = lastName;
    if (firstName == null) {
      throw new NullPointerException("Null firstName");
    }
    this.firstName = firstName;
    this.birthYear = birthYear;
  }
 
  @Override
  String lastName() {
    return lastName;
  }
 
  @Override
  String firstName() {
    return firstName;
  }
 
  @Override
  long birthYear() {
    return birthYear;
  }
 
  @Override
  public String toString() {
    return "Person{"
        + "lastName=" + lastName + ", "
        + "firstName=" + firstName + ", "
        + "birthYear=" + birthYear
        + "}";
  }
 
  @Override
  public boolean equals(Object o) {
    if (o == this) {
      return true;
    }
    if (o instanceof Person) {
      Person that = (Person) o;
      return (this.lastName.equals(that.lastName()))
           && (this.firstName.equals(that.firstName()))
           && (this.birthYear == that.birthYear());
    }
    return false;
  }
 
  @Override
  public int hashCode() {
    int h = 1;
    h *= 1000003;
    h ^= this.lastName.hashCode();
    h *= 1000003;
    h ^= this.firstName.hashCode();
    h *= 1000003;
    h ^= (this.birthYear >>> 32) ^ this.birthYear;
    return h;
  }
 
}

Из рассмотрения сгенерированного кода можно сделать несколько наблюдений:

  • Сгенерированный класс расширяет (наследование реализации) абстрактный класс, написанный вручную, что позволяет потребляющему коду использовать API рукописного класса, не зная, что сгенерированный класс используется.
  • Поля были сгенерированы, хотя поля не были определены непосредственно в исходном классе; AutoValue интерпретирует поля из предоставленных abstract методов доступа.
  • Сгенерированный класс не предоставляет методы «set» / mutator для полей (методы get / accessor). Это намеренное дизайнерское решение AutoValue, поскольку ключевой концепцией объектов Value является то, что они являются неизменяемыми.
  • Реализации equals (Object) , hashCode () и toString () автоматически генерируются соответствующим образом для каждого поля с учетом его типа.
  • Комментарии Javadoc к исходному классу и методы не воспроизводятся в сгенерированном классе расширения.

Одним из основных преимуществ использования такого подхода, как генерация AutoValue, является то, что разработчики могут сосредоточиться на более простых концепциях более высокого уровня, которые должен поддерживать конкретный класс, а генерация кода гарантирует, что детали нижнего уровня будут реализованы последовательно и правильно. Тем не менее, при использовании этого подхода следует помнить о некоторых вещах, и раздел «Передовой опыт » этого документа является хорошим местом для чтения на ранней стадии, чтобы узнать, работают ли предположения AutoValue для вашего собственного случая.

  • AutoValue, скорее всего, будет полезным, когда разработчики достаточно дисциплинированы, чтобы просматривать и поддерживать abstract «исходный» класс Java вместо сгенерированного класса.
    • Изменения в сгенерированных классах будут перезаписаны в следующий раз, когда обработка аннотаций снова сгенерирует класс, или генерация этого класса должна быть остановлена, чтобы этого не произошло.
    • Абстрактный «исходный» класс содержит документацию и другие высокоуровневые элементы, на которых большинство разработчиков захотят сосредоточиться, а сгенерированный класс просто реализует мельчайшие детали.
  • Вы захотите настроить свою сборку / IDE так, чтобы сгенерированные классы считались «исходным кодом», чтобы abstract класс компилировался.
  • Следует соблюдать особую осторожность при использовании изменяемых полей с AutoValue, если требуется сохранить неизменность (что обычно имеет место при выборе использования объектов-значений).
  • Просмотрите разделы « Лучшие практики» и « Как мне…», чтобы убедиться, что никакие конструктивные предположения AutoValue не делают его более подходящим для ваших потребностей.

Вывод

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