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 ?
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:
One possibility is to separate your interfaces surrounding such objects into an immutable variant (providing getters) and a mutable variant (providing getters and setters).
public interface Person {
String getName();
}
public interface MutablePerson extends Person {
void setName(String name);
}
It doesn't solve the mutability of the object per se but it does offer some guarantees that when you pass around the object using the immutable interface reference, you know that the code you're passing this to won't change your object. Obviously you need to control the references to the underlying object and determine the subset of functionality that has control of a reference via the mutable interface.
It doesn't solve the underlying problem and I would favour immutable objects until I definitely need a mutable version. The builder approach works nicely, and you can integrate it within the object to give a modifier thus:
Person newPerson = existingPerson.withAge(30);
Why not make your fields final and your modifier methods directly create new objects?
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With