我有一个案例,当我想要避免防御性副本,对于可能仍然被修改的数据,但通常只是阅读,而不是写入。因此,我想使用不可变对象,使用函数mutator方法,这是常见的(java lombok能够或多或少地自动执行)。我的进展方式如下:
public class Person {
private String name, surname;
public Person(String name, String surname) {....}
// getters...
// and instead of setters
public Person withName(String name) {
Person p= copy(); // create a copy of this...
p.name= name;
return p;
}
public Person copy() {....}
}
因此,要获得具有不同名称的人的副本,我会致电
p= new Person("Bar", "Alfred");
...
p= p.withName("Foo");
实际上,对象相当大(最后我使用序列化来避免编写复制代码的负担)。
现在,在浏览网页时,我发现这个实现存在潜在的并发问题,因为我的字段不是最终的,因此,并发访问可能会看到返回的副本,例如,没有新的名称更改(因为有在这种情况下,没有关于操作顺序的保证。)
当然,我不能使用当前的实现来完成我的字段,因为我先复制,然后更改副本中的数据。
所以,我正在为这个问题寻找一个好的解决方案。
我可能会使用volatile,但我觉得它不是一个好的解决方案。
另一种解决方案是使用构建器模式:
class PersonBuilder {
String name, surname; ....
}
public class Person {
private final String name, surname;
public Person(PersonBuilder builder) {...}
private PersonBuilder getBuilder() {
return new PersonBuilder(name, surname);
}
public Person withName(String name) {
PersonBuilder b= getBuilder();
b.setName(name);
return new Person(b);
}
}
这里有什么问题,最重要的是,是否有更优雅的方式做同样的事情?
答案 0 :(得分:3)
我建议您查看Guava的immutable collections,例如immutable list以及他们如何从构建器等创建列表。
成语如下:
List<String> list1 = ImmutableList.of("a","b","c"); // factory method
List<String> list2 = ImmutableList.builder() // builder pattern
.add("a")
.add("b")
.add("c")
.build();
List<String> list3 = ... // created by other means
List<String> immutableList3 = ImmutableList.copyOf(list3); // immutable copy, lazy if already immutable
我真的很喜欢上面的成语。对于实体构建器,我将采用以下方法:
Person johnWayne = Person.builder()
.firstName("John")
.lastName("Wayne")
.dob("05-26-1907")
.build();
Person johnWayneClone = johnWayne.copy() // returns a builder!
.dob("06-25-2014")
.build();
此处的构建器可以通过copy()
方法从现有实例获取,也可以通过Person
类(建议使用私有构造函数)的静态方法获取,返回人员构建器。
请注意,上面的内容模仿Scala的case classes,因为您可以从现有实例创建副本。
最后,不要忘记关注guidelines for immutable classes:
答案 1 :(得分:1)
一种可能性是将围绕这些对象的接口分成不可变的变体(提供getter)和可变的变体(提供getter和setter)。
public interface Person {
String getName();
}
public interface MutablePerson extends Person {
void setName(String name);
}
它没有解决对象本身的可变性,但它提供了一些保证,当你使用不可变接口引用传递对象时,你知道你传递给它的代码不会改变你的对象。显然,您需要控制对底层对象的引用,并确定通过可变接口控制引用的功能子集。
它没有解决潜在的问题,我宁愿使用不可变对象,直到我肯定需要一个可变版本。构建器方法很好地工作,您可以将它集成到对象中以提供修饰符:
Person newPerson = existingPerson.withAge(30);
答案 2 :(得分:1)
为什么不让你的领域成为最终,你的修饰语方法直接创建新对象?
public class Person {
private final String name, surname;
public Person(String name, String surname) {....}
// getters...
// and instead of setters
public Person withName(String newName) {
return new Person(newName, surname);
}
}
答案 3 :(得分:0)
你的问题归结为:你想要一个安全地发布一个有效不可变对象的有效不可变,几乎但不是非常忠实的副本的方法。
我会使用构建器解决方案:它很冗长,但是Eclipse会帮助它,并且它允许所有已发布的对象实际上不可变。实际的不变性使安全出版成为一个明智的选择。
如果我写了它,它看起来像这样:
class Person {
public static final FooType DEFAULT_FOO = ...;
public static final BarType DEFAULT_BAR = ...;
public static final BazType DEFAULT_BAZ = ...;
...
private final FooType foo;
private final BarType bar;
private final BazType baz;
...
private Person(Builder builder) {
this.foo = builder.foo;
this.bar = builder.bar;
this.baz = builder.baz;
...
}
public FooType getFoo() { return foo; }
public BarType getBar() { return bar; }
public BazType getBaz() { return baz; }
...
public Person cloneWith(FooType foo) {
return new Builder(this).setFoo(foo).build();
}
public Person cloneWith(BarType bar) {
return new Builder(this).setBar(bar).build();
}
public Person cloneWith(FooType foo, BarType bar) {
return new Builder(this).setFoo(foo).setBar(bar).build();
}
...
public class Builder{
private FooType foo;
private BarType bar;
private BazType baz;
...
public Builder() {
foo = DEFAULT_FOO;
bar = DEFAULT_BAR;
baz = DEFAULT_BAZ;
...
}
public Builder(Person person) {
foo = person.foo;
bar = person.bar;
baz = person.baz;
...
}
public Builder setFoo(FooType foo) {
this.foo = foo;
return this;
}
public Builder setBar(BarType bar) {
this.bar = bar;
return this;
}
public Builder setBaz(BazType baz) {
this.baz = baz;
return this;
}
...
public Person build() {
return new Person(this);
}
}
}
答案 4 :(得分:0)
取决于您打算更改的字段数。您可以创建特殊的已更改对象,如:
interface Person {
public String getForeName();
public String getSurName();
}
class RealPerson implements Person {
private final String foreName;
private final String surName;
public RealPerson (String foreName, String surName) {
this.foreName = foreName;
this.surName = surName;
}
@Override
public String getForeName() {
return foreName;
}
@Override
public String getSurName() {
return surName;
}
public Person setSurName (String surName) {
return new PersonWithSurnameChanged(this, surName);
}
}
class PersonWithSurnameChanged implements Person {
final Person original;
final String surName;
public PersonWithSurnameChanged (Person original, String surName) {
this.original = original;
this.surName = surName;
}
@Override
public String getForeName() {
return original.getForeName();
}
@Override
public String getSurName() {
return surName;
}
}
这也可以缓解克隆重物的问题。