概述
本文将展示如何使用Immutables库。该库包含生成和处理可序列化且可定制的不可变对象的注解和注解处理器。
2. Maven依赖
要在项目中使用Immutables,您需要在pom.xml
文件的dependencies
部分添加以下依赖:
<dependency>
<groupId>org.immutables</groupId>
<artifactId>value</artifactId>
<version>2.2.10</version>
<scope>provided</scope>
</dependency>
由于该模块在运行时并不需要,建议指定provided
范围。库的最新版本可以在这里找到。
3. Immutables
该库根据抽象类型(接口、类、注解)生成不可变对象。关键在于正确使用@Value.Immutable
注解。它会为被注解的类型生成一个不可变版本,并在其名称前加上Immutable
前缀。
如果我们尝试为名为“X”的类生成不可变版本,它将生成一个名为ImmutableX
的类。生成的类不是递归不可变的,因此需要注意这一点。
提醒一下,因为Immutables利用了注解处理,所以请确保在您的IDE中启用注解处理。
3.1. 使用@Value.Immutable
与抽象类和接口
首先,我们创建一个简单的抽象类Person
,其中包含两个表示将要生成字段的抽象访问方法,然后用@Value.Immutable
注解该类:
@Value.Immutable
public abstract class Person {
abstract String getName();
abstract Integer getAge();
}
完成注解处理后,我们可以在target/generated-sources
目录中找到已经准备好的、新生成的ImmutablePerson
类:
@Generated({"Immutables.generator", "Person"})
public final class ImmutablePerson extends Person {
private final String name;
private final Integer age;
private ImmutablePerson(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
String getName() {
return name;
}
@Override
Integer getAge() {
return age;
}
// toString, hashcode, equals, copyOf and Builder omitted
}
生成的类带有实现的toString()
、hashCode()
、equals()
方法,以及一个ImmutablePerson.Builder
构建器。请注意,生成的构造函数具有private
访问权限。
为了构造ImmutablePerson
类的实例,我们需要使用构建器或静态方法ImmutablePerson.copyOf()
,它可以将Person
对象转换为ImmutablePerson
的副本。
如果我们要使用构建器来构造实例,可以简单地编写:
ImmutablePerson john = ImmutablePerson.builder()
.age(42)
.name("John")
.build();
生成的类是不可变的,这意味着它们不能修改。如果您想修改已存在的对象,可以使用其中一个“withX”方法,这些方法不会修改原始对象,而是创建一个具有修改字段的新实例。
让我们更新john
的年龄并创建一个新的john43
对象:
ImmutablePerson john43 = john.withAge(43);
在这种情况下,以下断言将是正确的:
assertThat(john).isNotSameAs(john43);
assertThat(john.getAge()).isEqualTo(42);
4. 其他工具
如果没有能力进行自定义,这样的类生成将不太实用。Immutables库提供了一系列额外的注解,可用于定制@Value.Immutable
的输出。有关所有注解的详细信息,请参阅Immutables的文档。
4.1. @Value.Parameter
注解
@Value.Parameter
注解可用于指定应生成构造方法的字段。
如果像这样注解您的类:
@Value.Immutable
public abstract class Person {
@Value.Parameter
abstract String getName();
@Value.Parameter
abstract Integer getAge();
}
则可以如下方式实例化它:
ImmutablePerson.of("John", 42);
4.2. @Value.Default
注解
@Value.Default
注解允许您指定在初始值未提供时应使用的默认值。要做到这一点,您需要创建一个非抽象的访问方法,返回固定的值,并使用@Value.Default
注解:
@Value.Immutable
public abstract class Person {
abstract String getName();
@Value.Default
Integer getAge() {
return 42;
}
}
以下断言将为真:
ImmutablePerson john = ImmutablePerson.builder()
.name("John")
.build();
assertThat(john.getAge()).isEqualTo(42);
4.3. @Value.Auxiliary
注解
@Value.Auxiliary
注解可用于注解一个将在对象实例中存储但会被equals()
、hashCode()
和toString()
实现忽略的属性。
如果像这样注解您的类:
@Value.Immutable
public abstract class Person {
abstract String getName();
abstract Integer getAge();
@Value.Auxiliary
abstract String getAuxiliaryField();
}
当使用辅助字段时,以下断言将为真:
ImmutablePerson john1 = ImmutablePerson.builder()
.name("John")
.age(42)
.auxiliaryField("Value1")
.build();
ImmutablePerson john2 = ImmutablePerson.builder()
.name("John")
.age(42)
.auxiliaryField("Value2")
.build();
assertThat(john1.equals(john2)).isTrue();
assertThat(john1.toString()).isEqualTo(john2.toString());
assertThat(john1.hashCode()).isEqualTo(john2.hashCode());
4.4. @Value.Immutable(Prehash = True)
注解
由于我们的生成类是不可变的,永远不会被修改,所以hashCode()
的结果始终不变,只能在对象实例化时计算一次。
如果这样注解您的类:
@Value.Immutable(prehash = true)
public abstract class Person {
abstract String getName();
abstract Integer getAge();
}
检查生成的类时,您会看到hashCode()
值现在已被预计算并存储在一个字段中:
@Generated({"Immutables.generator", "Person"})
public final class ImmutablePerson extends Person {
private final String name;
private final Integer age;
private final int hashCode;
private ImmutablePerson(String name, Integer age) {
this.name = name;
this.age = age;
this.hashCode = computeHashCode();
}
// generated methods
@Override
public int hashCode() {
return hashCode;
}
}
hashCode()
方法返回的是对象构造时生成的预计算hashCode
。
5. 总结
在这篇快速教程中,我们展示了Immutables库的基本用法。
文章中的所有源代码和单元测试可在GitHub仓库中找到。