“只设置一次”的要求感觉有点武断。我相当确定您正在寻找的是一个从未初始化状态永久转换为已初始化状态的类。毕竟,多次设置对象的 id 可能很方便(通过代码重用或其他方式),只要在“构建”对象后不允许更改 id。
一种相当合理的模式是在单独的字段中跟踪这种“构建”状态:
public final class Example {
private long id;
private boolean isBuilt;
public long getId() {
return id;
}
public void setId(long id) {
if (isBuilt) throw new IllegalArgumentException("already built");
this.id = id;
}
public void build() {
isBuilt = true;
}
}
用法:
Example e = new Example();
// do lots of stuff
e.setId(12345L);
e.build();
// at this point, e is immutable
使用这种模式,您可以构造对象,设置它的值(方便的次数),然后调用build()
“immutify”它。
与您的初始方法相比,此模式有几个优点:
- 没有用于表示未初始化字段的魔法值。例如,与任何其他值
0
一样有效的 id 。long
- 设置者具有一致的行为。在
build()
被调用之前,它们工作。调用后build()
,无论您传递什么值,它们都会抛出。(为方便起见,请注意使用未经检查的异常)。
- 该类被标记
final
,否则开发人员可以扩展您的类并覆盖设置器。
但是这种方法有一个相当大的缺点:使用此类的开发人员在编译时无法知道特定对象是否已初始化。当然,您可以添加一个isBuilt()
方法,以便开发人员可以在运行时检查对象是否已初始化,但在编译时知道这些信息会方便得多。为此,您可以使用构建器模式:
public final class Example {
private final long id;
public Example(long id) {
this.id = id;
}
public long getId() {
return id;
}
public static class Builder {
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public Example build() {
return new Example(id);
}
}
}
用法:
Example.Builder builder = new Example.Builder();
builder.setId(12345L);
Example e = builder.build();
由于以下几个原因,这要好得多:
- 我们正在使用
final
字段,因此编译器和开发人员都知道这些值不能更改。
- 对象的初始化和未初始化形式之间的区别是通过 Java 的类型系统来描述的。一旦对象被构建,就没有设置器来调用它。
- 已构建类的实例保证线程安全。
是的,维护起来有点复杂,但恕我直言,好处大于成本。