使用不可变对象创建值对象
在回答我最近的文章中AutoValue:生成的不可變的值類 , 布蘭登認為,這可能是有趣的,看看如何AutoValue比較項目Lombok和Immutables和凱文借調這一點。 我同意這是一個好主意,但是我首先將這篇文章發布為Immutables的簡要概述,因為我已經為Lombok和AutoValue提供了類似的文章。
可從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*/@OverrideString lastName() {return lastName;}/*** @return The value of the {@code firstName} attribute*/@OverrideString firstName() {return firstName;}/*** @return The value of the {@code birthYear} attribute*/@Overridelong 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*/@Overridepublic 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*/@Overridepublic 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*/@Overridepublic 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
總結
以上是生活随笔為你收集整理的使用不可变对象创建值对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苹果手机忘记密码锁屏了怎么办
- 下一篇: 栅栏的英文是什么意思 栅栏的英文简单介绍