Search code examples
javamultithreadingimmutabilityfinalvolatile

Good way to create a immutable class with modifiers (thread-safe)


I have a case when I want to avoid defensive copies, for data which might nevertheless be modified, but is usually simply read, and not written to. So, I'd like to use immutable objects, with functional mutator methods, which is kind of usual (java lombok is able to do it more or less automatically). The way I proceed is the following:

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() {....}         
}

So, to get a copy of the person with a different name, I would call

p= new Person("Bar", "Alfred");
...
p= p.withName("Foo");

In practice, the objects are rather large (and I ended up using serialization to avoid the burden of writing the copy code).

Now, while browsing the web, I see a potential concurrency problem with this implementation, as my fields are not final, and thus, concurrent access might see the returned copy, for instance, without the new name change (as there is no warrantee on the order of operation in this context).

Of course, I can't make my fields final, with the current implementation, as I first do a copy, and then change the data in the copy.

So, I'm looking for a good solution for this problem.

I might use volatile, but I feel it's not a good solution.

Another solution would be to use the builder pattern:

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);
  }
}

Is there any problem here, and above all, is there a more elegant way of doing the same thing ?


Solution

  • I recommend you take a look at Guava's immutable collections, such as immutable list and how they create lists from builders etc.

    The idiom is the following:

    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
    

    I really like the idiom above. For an entity builder I would take the following approach:

    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();
    

    The builder here can be obtained from an existing instance via the copy() method or via a static method on the Person class (a private constructor is recommended) that return a person builder.

    Note that the above mimics a little Scala's case classes in that you can create a copy from an existing instance.

    Finally, don't forget to follow the guidelines for immutable classes:

    • make the class final or make all getters final (if the class can be extended);
    • make all fields final and private;
    • initialize all fields in the constructor (which can be private if you provide a builder and/or factory methods);
    • make defensive copies from getters if returning mutable objects (mutable collections, dates, third party classes, etc.).