使用不可变对象创建值对象

在回答我最近的文章中AutoValue:生成的不可变的值类布兰登认为,这可能是有趣的,看看如何AutoValue比较项目LombokImmutables凯文借调这一点。 我同意这是一个好主意,但是我首先将这篇文章发布为Immutables的简要概述,因为我已经为LombokAutoValue提供了类似的文章。

可从Maven中央存储库中获得Immutables 2.2.5其许可证页面指出“ Imputables工具箱和所有必需的依赖项均在Apache软件许可2.0版中涵盖。” 开始吧! 网页指出“运行Immutables注释处理器需要Java 7或更高版本。”

像AutoValue这样的不可变对象使用编译时注释来生成定义不可变对象的类的源代码。 因为它们都使用这种方法,所以都只引入了编译时依赖性,并且在应用程序的运行时类路径上不需要它们各自的JAR。 换句话说,不可变的JAR必须位于编译器( javac )的类路径上,而不是位于Java启动器( java )的类路径上。

下一个代码清单( Person.java )中显示了“模板” Person类的代码清单。 它看起来与我在AutoValue演示中使用的Person.java非常相似。

人.java

package dustin.examples.immutables;

import org.immutables.value.Value;

/**
 * Represents an individual as part of demonstration of
 * the Immutables project (http://immutables.github.io/).
 */
@Value.Immutable  // concrete extension will be generated by Immutables
abstract class Person
{
   /**
    * 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帖子中列出的“ template”类和“ template”类的唯一区别是包的名称,正在演示哪个产品的Javadoc注释以及(最重要的是)导入并应用于类。 在AutoValue示例中有一个特定的“创建”方法不在Immutables示例中,但这只是因为我没有演示AutoValue生成器的使用,这将使“ create”方法变得不必要。

当我在类路径上适当地指定使用Immutables并使用javac编译以上源代码时,将调用注释处理器并生成以下Java源代码:

ImmutablePerson.java

package dustin.examples.immutables;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.annotation.Generated;

/**
 * Immutable implementation of {@link Person}.
 * <p>
 * Use the builder to create immutable instances:
 * {@code ImmutablePerson.builder()}.
 */
@SuppressWarnings("all")
@Generated({"Immutables.generator", "Person"})
final class ImmutablePerson extends Person {
  private final String lastName;
  private final String firstName;
  private final long birthYear;

  private ImmutablePerson(String lastName, String firstName, long birthYear) {
    this.lastName = lastName;
    this.firstName = firstName;
    this.birthYear = birthYear;
  }

  /**
   * @return The value of the {@code lastName} attribute
   */
  @Override
  String lastName() {
    return lastName;
  }

  /**
   * @return The value of the {@code firstName} attribute
   */
  @Override
  String firstName() {
    return firstName;
  }

  /**
   * @return The value of the {@code birthYear} attribute
   */
  @Override
  long birthYear() {
    return birthYear;
  }

  /**
   * Copy the current immutable object by setting a value for the {@link Person#lastName() lastName} attribute.
   * An equals check used to prevent copying of the same value by returning {@code this}.
   * @param lastName A new value for lastName
   * @return A modified copy of the {@code this} object
   */
  public final ImmutablePerson withLastName(String lastName) {
    if (this.lastName.equals(lastName)) return this;
    String newValue = Objects.requireNonNull(lastName, "lastName");
    return new ImmutablePerson(newValue, this.firstName, this.birthYear);
  }

  /**
   * Copy the current immutable object by setting a value for the {@link Person#firstName() firstName} attribute.
   * An equals check used to prevent copying of the same value by returning {@code this}.
   * @param firstName A new value for firstName
   * @return A modified copy of the {@code this} object
   */
  public final ImmutablePerson withFirstName(String firstName) {
    if (this.firstName.equals(firstName)) return this;
    String newValue = Objects.requireNonNull(firstName, "firstName");
    return new ImmutablePerson(this.lastName, newValue, this.birthYear);
  }

  /**
   * Copy the current immutable object by setting a value for the {@link Person#birthYear() birthYear} attribute.
   * A value equality check is used to prevent copying of the same value by returning {@code this}.
   * @param birthYear A new value for birthYear
   * @return A modified copy of the {@code this} object
   */
  public final ImmutablePerson withBirthYear(long birthYear) {
    if (this.birthYear == birthYear) return this;
    return new ImmutablePerson(this.lastName, this.firstName, birthYear);
  }

  /**
   * This instance is equal to all instances of {@code ImmutablePerson} that have equal attribute values.
   * @return {@code true} if {@code this} is equal to {@code another} instance
   */
  @Override
  public boolean equals(Object another) {
    if (this == another) return true;
    return another instanceof ImmutablePerson
        && equalTo((ImmutablePerson) another);
  }

  private boolean equalTo(ImmutablePerson another) {
    return lastName.equals(another.lastName)
        && firstName.equals(another.firstName)
        && birthYear == another.birthYear;
  }

  /**
   * Computes a hash code from attributes: {@code lastName}, {@code firstName}, {@code birthYear}.
   * @return hashCode value
   */
  @Override
  public int hashCode() {
    int h = 31;
    h = h * 17 + lastName.hashCode();
    h = h * 17 + firstName.hashCode();
    h = h * 17 + Long.hashCode(birthYear);
    return h;
  }

  /**
   * Prints the immutable value {@code Person} with attribute values.
   * @return A string representation of the value
   */
  @Override
  public String toString() {
    return "Person{"
        + "lastName=" + lastName
        + ", firstName=" + firstName
        + ", birthYear=" + birthYear
        + "}";
  }

  /**
   * Creates an immutable copy of a {@link Person} value.
   * Uses accessors to get values to initialize the new immutable instance.
   * If an instance is already immutable, it is returned as is.
   * @param instance The instance to copy
   * @return A copied immutable Person instance
   */
  public static ImmutablePerson copyOf(Person instance) {
    if (instance instanceof ImmutablePerson) {
      return (ImmutablePerson) instance;
    }
    return ImmutablePerson.builder()
        .from(instance)
        .build();
  }

  /**
   * Creates a builder for {@link ImmutablePerson ImmutablePerson}.
   * @return A new ImmutablePerson builder
   */
  public static ImmutablePerson.Builder builder() {
    return new ImmutablePerson.Builder();
  }

  /**
   * Builds instances of type {@link ImmutablePerson ImmutablePerson}.
   * Initialize attributes and then invoke the {@link #build()} method to create an
   * immutable instance.
   * <p><em>{@code Builder} is not thread-safe and generally should not be stored in a field or collection,
   * but instead used immediately to create instances.</em>
   */
  static final class Builder {
    private static final long INIT_BIT_LAST_NAME = 0x1L;
    private static final long INIT_BIT_FIRST_NAME = 0x2L;
    private static final long INIT_BIT_BIRTH_YEAR = 0x4L;
    private long initBits = 0x7L;

    private String lastName;
    private String firstName;
    private long birthYear;

    private Builder() {
    }

    /**
     * Fill a builder with attribute values from the provided {@code Person} instance.
     * Regular attribute values will be replaced with those from the given instance.
     * Absent optional values will not replace present values.
     * @param instance The instance from which to copy values
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder from(Person instance) {
      Objects.requireNonNull(instance, "instance");
      lastName(instance.lastName());
      firstName(instance.firstName());
      birthYear(instance.birthYear());
      return this;
    }

    /**
     * Initializes the value for the {@link Person#lastName() lastName} attribute.
     * @param lastName The value for lastName 
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder lastName(String lastName) {
      this.lastName = Objects.requireNonNull(lastName, "lastName");
      initBits &= ~INIT_BIT_LAST_NAME;
      return this;
    }

    /**
     * Initializes the value for the {@link Person#firstName() firstName} attribute.
     * @param firstName The value for firstName 
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder firstName(String firstName) {
      this.firstName = Objects.requireNonNull(firstName, "firstName");
      initBits &= ~INIT_BIT_FIRST_NAME;
      return this;
    }

    /**
     * Initializes the value for the {@link Person#birthYear() birthYear} attribute.
     * @param birthYear The value for birthYear 
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder birthYear(long birthYear) {
      this.birthYear = birthYear;
      initBits &= ~INIT_BIT_BIRTH_YEAR;
      return this;
    }

    /**
     * Builds a new {@link ImmutablePerson ImmutablePerson}.
     * @return An immutable instance of Person
     * @throws java.lang.IllegalStateException if any required attributes are missing
     */
    public ImmutablePerson build() {
      if (initBits != 0) {
        throw new IllegalStateException(formatRequiredAttributesMessage());
      }
      return new ImmutablePerson(lastName, firstName, birthYear);
    }

    private String formatRequiredAttributesMessage() {
      List<String> attributes = new ArrayList<String>();
      if ((initBits & INIT_BIT_LAST_NAME) != 0) attributes.add("lastName");
      if ((initBits & INIT_BIT_FIRST_NAME) != 0) attributes.add("firstName");
      if ((initBits & INIT_BIT_BIRTH_YEAR) != 0) attributes.add("birthYear");
      return "Cannot build Person, some of required attributes are not set " + attributes;
    }
  }
}

通过检查生成的代码,可以得出一些结论(您会发现它们与我之前的文章中为AutoValue列出的观察结果非常相似):

  • 生成的类扩展(实现继承)了手写的抽象类,从而允许使用代码使用手写类的API,而不必知道正在使用生成的类。
  • 即使没有在源类中直接定义任何字段,也将生成字段; 不可变对象根据提供的abstract访问器方法解释字段。
  • 生成的类不为字段提供“设置” / mutator方法(get / accessor方法)。 这并不奇怪,因为值对象的关键概念是它们是不可变的,甚至这个项目的名称( Immutables )也暗示了这一特征。 请注意, Immutables确实为具有@ Value.Modifiable批注的可修改对象提供了一些功能。
  • 考虑到每个字段的类型,将自动为每个字段适当地生成equals(Object)hashCode()toString()的实现。
  • 在源类和方法上的Javadoc注释不会在生成的扩展类上重现。 相反,在生成的类的方法上提供了更简单(更通用)的Javadoc注释,在构建器类的方法上提供了更重要(但仍是通用的)的Javadoc注释。

正如我关于AutoValue所述,使用诸如Immutables生成之类的方法的主要优点之一是,开发人员可以专注于特定类应支持的更高级的概念,并且代码生成可以确保较低级别的细节。一致且正确地实施。 但是,使用这种方法时要记住一些事情。

  • 当开发人员受过足够的训练以检查和维护抽象的“源” Java类而不是生成的类时, 不可变对象最有帮助。
    • 下次注释处理再次生成该类时,对生成类的更改将被覆盖,否则必须停止该类的生成,以免发生这种情况。
  • 您将需要设置build / IDE,以便将生成的类视为“源代码”,以便抽象类可以编译,并且对生成的类的任何依赖项也可以编译。
  • 如果要将可变字段与不可变项一起使用,则必须特别小心(如果通常要选择使用不可变项或值对象,通常就是这种情况)。

结论

我的结论几乎与我在AutoValue上发表的文章一样。 Immutables允许开发人员编写更简洁的代码,重点放在高级细节上,并将繁琐的底层(通常是容易出错的)细节委派给Immutables ,以实现自动代码生成。 这类似于IDE的源代码生成可以执行的操作,但是Immutables相对于IDE方法的优势是Immutables可以在每次编译代码时重新生成源代码,从而使生成的代码保持最新。 Immutables的这一优势也是Java自定义注释处理功能强大的一个很好的例子。

翻译自: https://www.javacodegeeks.com/2016/06/creating-value-objects-immutables.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值