使用修饰符创建不可变类的好方法(线程安全)

kha*_*set 6 java multithreading final volatile immutability

我有一个案例,当我想避免防御性副本,对于可能仍然被修改的数据,但通常只是阅读,而不是写入.所以,我想使用不可变对象,使用函数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() {....}         
}
Run Code Online (Sandbox Code Playgroud)

因此,要获得具有不同名称的人的副本,我会打电话

p= new Person("Bar", "Alfred");
...
p= p.withName("Foo");
Run Code Online (Sandbox Code Playgroud)

在实践中,对象相当大(我最终使用序列化来避免编写复制代码的负担).

现在,在浏览网页时,我发现这个实现存在潜在的并发问题,因为我的字段不是最终的,因此,并发访问可能会看到返回的副本,例如,没有新的名称更改(因为没有保证在这种情况下的操作顺序).

当然,我无法使用当前实现将我的字段设为最终,因为我先复制,然后更改副本中的数据.

所以,我正在为这个问题寻找一个好的解决方案.

我可能会使用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);
  }
}
Run Code Online (Sandbox Code Playgroud)

这里有什么问题,最重要的是,是否有更优雅的方式做同样的事情?

Gio*_*tta 3

我建议您看一下 Guava 的不可变集合,例如不可变列表以及它们如何从构建器创建列表等。

习语如下:

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
Run Code Online (Sandbox Code Playgroud)

我真的很喜欢上面的成语。对于实体构建器,我将采取以下方法:

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();
Run Code Online (Sandbox Code Playgroud)

copy()这里的构建器可以通过返回个人构建器的方法或类上的静态方法Person(建议使用私有构造函数)从现有实例获取。

请注意,上面模拟了一点 Scala 的案例类,您可以从现有实例创建副本。

最后,不要忘记遵循不可变类的准则

  • 使类最终使所有吸气剂最终(如果类可以扩展);
  • 将所有字段设为最终字段和私有字段;
  • 初始化构造函数中的所有字段(如果您提供构建器和/或工厂方法,则可以是私有的);
  • 如果返回可变对象(可变集合、日期、第三方类等),请从 getters 制作防御性副本。