Java 如何为最终变量使用setter而不是构造函数?
我正在解析一个XML文件,其中我希望不可变的字段ID必须在对象创建后设置。我应该将其设置为null,并在setID()方法中抛出一个异常(如果ID=空的 编辑: 我正在解析一个XML文件,在开始时,我创建了一个对象,其字段和对象是使用XML文件中的信息填充的。我希望能够在创建根对象后设置ID,该ID应该是不可变的Java 如何为最终变量使用setter而不是构造函数?,java,oop,immutability,final,Java,Oop,Immutability,Final,我正在解析一个XML文件,其中我希望不可变的字段ID必须在对象创建后设置。我应该将其设置为null,并在setID()方法中抛出一个异常(如果ID=空的 编辑: 我正在解析一个XML文件,在开始时,我创建了一个对象,其字段和对象是使用XML文件中的信息填充的。我希望能够在创建根对象后设置ID,该ID应该是不可变的 编辑:将“final”改为“immutable”,因为这就是我在语义上的真正意思。(抱歉:()您不能在构造函数之外更改最终成员。您必须使其不是最终成员。您绝对不能使其成为最终成员。从s
编辑:将“final”改为“immutable”,因为这就是我在语义上的真正意思。(抱歉:()您不能在构造函数之外更改最终成员。您必须使其不是最终成员。您绝对不能使其成为最终成员。
从setID()引发异常)
如果ID!=null
是个好主意。如果您提供更多细节,也许有人可以想出更具创造性的解决方案?根据定义,最终字段在构建对象后不会更改。
如果您真正的意思是希望该字段设置一次,那么您可以简单地将该字段初始化为null,如果该字段不再为null,则让该字段的setter引发异常。最常见的解决方法是。您使用setter构建一个对象,然后在该对象准备好后,使用e builder作为模板。除非在构建过程中初始化,否则无法声明该字段为最终字段。我认为类似的内容可能满足您的要求
class Foo {
private Bar x;
void setX(Bar x) {
if (x == null)
throw new IllegalArgumentException();
if (this.x != null)
throw new IllegalStateException();
this.x = x;
}
}
此Foo
实现旨在通过单个线程进行访问。对于多线程访问,可以使用AtomicReference
或外部同步
访问
另外请注意,如果null
是x
的有效值,您可以创建一个私有的、虚拟的Bar
实例,并使用它来代替null,也许这个很好的库将帮助您解析xml,因此您不必费心创建对象。我不知道您是否可以按您想要的方式配置它,但我当然值得一看
我会尽量避免使用公共的setId方法,因为如果没有客户端可以调用的方法,就可以解决这个问题。试着用这种语言告诉用户他可以做什么,不应该做什么
如果不在xml文件中存储id,则应使用创建对象并使用参数化构造函数的工厂
如果您没有为id提供设置器,用户只能在创建对象时“设置”id,我认为这就是您想要的。更好的方法可能是使用生成器,如第2项所述
基本思想是让一个生成器类为不同的构造函数参数提供setter(但通常不是getter)。还有一个build()
方法。生成器类通常是用于构建的类的(静态)嵌套类。外部类的构造函数通常是私有的
最终结果如下所示:
public class Foo {
public static class Builder {
public Foo build() {
return new Foo(this);
}
public Builder setId(int id) {
this.id = id;
return this;
}
// you can set defaults for these here
private int id;
}
public static Builder builder() {
return new Builder();
}
private Foo(Builder builder) {
id = builder.id;
}
private final int id;
// The rest of Foo goes here...
}
Foo foo = Foo.builder()
.setId(id)
.build();
要创建Foo的实例,请编写如下内容:
public class Foo {
public static class Builder {
public Foo build() {
return new Foo(this);
}
public Builder setId(int id) {
this.id = id;
return this;
}
// you can set defaults for these here
private int id;
}
public static Builder builder() {
return new Builder();
}
private Foo(Builder builder) {
id = builder.id;
}
private final int id;
// The rest of Foo goes here...
}
Foo foo = Foo.builder()
.setId(id)
.build();
当然,您也可以将其拆分:
// I don't know the ID yet, but I want a place to store it.
Foo.Builder fooBuilder = Foo.builder();
...
// Now I know the ID:.
fooBuilder.setId(id);
...
// Now I'm done and want an immutable Foo.
Foo foo = fooBuilder.build();
您可以在生成器上有多个setter,甚至可以向build()或生成器的构造函数添加其他参数
这允许您在解析时拥有一个可变对象,但在完成后切换到一个不可变对象。您的API在许多情况下只需要公开不可变对象。从技术上讲,如果您可以在执行构造函数后更改值,则它不是“不可变的”。它更像是“半可变的”
撇开术语不谈:我想到的第一个想法是使用某种“验证”函数。检查所有设置程序,当您认为完成时,调用validate。如果失败,则表示对象缺少必填字段或其他内容
构建器对象的想法也很好。我从未使用过这种模式。我必须仔细考虑利弊。我应该引发什么样的异常?我应该引发什么样的异常?@Aymon:这取决于你。如果这可能发生在系统中的多个类或字段中,请定义一个自定义公共类型,例如RepeatedAssignmentException@Aymon:我可能会使用IllegalStateException,或者从它派生的东西。我使用Google Guava checkArguments。它会抛出IllegalArgumentException,请参阅:我还有许多其他设置的字段,在我到达我想要存储在不可变字段中的信息之前它们不是不可变的。这是一个很好的解决方案awback可能是重复大量代码的繁琐过程,您可以将数据放入XML处理程序实例中。