Ric*_*ell 37 java final reference
我想要一个类,我可以使用一个变量unset(the id)创建实例,然后稍后初始化此变量,并在初始化后使其不可变.实际上,我想要一个final我可以在构造函数之外初始化的变量.
目前,我正在通过一个Exception如下所示的setter来即兴创作:
public class Example {
private long id = 0;
// Constructors and other variables and methods deleted for clarity
public long getId() {
return id;
}
public void setId(long id) throws Exception {
if ( this.id == 0 ) {
this.id = id;
} else {
throw new Exception("Can't change id once set");
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个很好的方式来实现我想要做的事情吗?我觉得我应该能够在初始化后设置一些不可变的东西,或者我可以使用一种模式来使它更优雅.
And*_*niy 28
让我建议你一点点优雅的决定.第一个变种(没有抛出异常):
public class Example {
private Long id;
// Constructors and other variables and methods deleted for clarity
public long getId() {
return id;
}
public void setId(long id) {
this.id = this.id == null ? id : this.id;
}
}
Run Code Online (Sandbox Code Playgroud)
第二个变种(抛出异常):
public void setId(long id) {
this.id = this.id == null ? id : throw_();
}
public int throw_() {
throw new RuntimeException("id is already set");
}
Run Code Online (Sandbox Code Playgroud)
cam*_*ecc 10
"只设置一次"的要求感觉有点武断.我很确定你要找的是一个从未初始化状态永久转换到初始化状态的类.毕竟,只要在"构建"对象之后不允许更改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;
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
Example e = new Example();
// do lots of stuff
e.setId(12345L);
e.build();
// at this point, e is immutable
Run Code Online (Sandbox Code Playgroud)
使用此模式,您可以构造对象,设置其值(方便的次数),然后调用build()"immutify"它.
与初始方法相比,此模式有几个优点:
0与任何其他long值一样有效.build()被叫之前,他们工作.在build()被叫之后,无论你传递什么值,他们都会抛出.(为方便起见,请注意使用未经检查的异常).final,否则开发人员可以扩展您的类并覆盖setter.但是这种方法有一个相当大的缺点:使用这个类的开发人员在编译时无法知道特定对象是否已经初始化.当然,您可以添加一个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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
Example.Builder builder = new Example.Builder();
builder.setId(12345L);
Example e = builder.build();
Run Code Online (Sandbox Code Playgroud)
由于以下几个原因,这要好得多:
final字段,因此编译器和开发人员都知道这些值无法更改.是的,维护起来有点复杂,但恕我直言,其好处超过了成本.
我最近在编写一些代码来构造一个不可变的循环图(其中边引用其节点)时遇到了这个问题。我还注意到这个问题的现有答案都不是线程安全的(这实际上允许多次设置该字段),所以我想我会贡献我的答案。基本上,我只是创建了一个名为 的包装器类FinalReference,它包装了AtomicReference并利用 的AtomicReference方法compareAndSet()。通过调用compareAndSet(null, newValue),您可以确保多个并发修改线程最多设置一次新值。该调用是原子的,并且仅当现有值为 null 时才会成功。请参阅下面的示例源代码FinalReference和示例测试代码的 Github 链接来演示正确性。
public final class FinalReference<T> {
private final AtomicReference<T> reference = new AtomicReference<T>();
public FinalReference() {
}
public void set(T value) {
this.reference.compareAndSet(null, value);
}
public T get() {
return this.reference.get();
}
}
Run Code Online (Sandbox Code Playgroud)