A good way to create an immutable class with modifiers (thread safe)

I have a case where I want to avoid protective copies, for data that, however, can be changed, but usually just read and write. Thus, I would like to use immutable objects with functional mutator methods, which are commonplace (java lombok is able to do this more or less automatically). I continue with 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 a person with a different name, I would call

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

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

Now, looking at the web interface, I see a potential concurrency problem with this implementation, as my fields are not final, and thus simultaneous access can see the returned copy, for example, without changing the new name (as in this context there is no guarantee on operating procedure).

Of course, I cannot make my fields final, with the current implementation, since I first make a copy and then modify the data in the copy.

So, I am looking for a good solution to this problem.

I can use volatile, but I think this is not a good solution.

Another solution would be to use a 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 a problem here, and above all, is there a more elegant way to do the same?

+6
source share
5 answers

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

The idiom is this:

 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 object constructor, I would do 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 using the copy() method or using the static method of the Person class (using a private constructor is recommended), which returns the person constructor.

Note that the above imitates a little Scala case classes , in which you can create a copy from an existing instance.

Finally, be sure 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 builder and / or factory methods);
  • Create protective copies from getters if you return mutable objects (mutable collections, dates, third-party classes, etc.).
+3
source

One possibility is to separate your interfaces surrounding such objects into an immutable option (providing getters) and a mutable option (providing getters and setters).

 public interface Person { String getName(); } public interface MutablePerson extends Person { void setName(String name); } 

It does not solve the variability of an object as such, but it offers some guarantees that when you pass an object using a reference to an immutable interface, you know that the code you pass to this will not change your object. Obviously, you need to control the links to the base object and define a subset of functionality that controls the link through a mutable interface.

It does not solve the main problem, and I would suggest immutable objects until I definitely do not need a mutable version. The builder approach works great, and you can integrate it inside the object to give a modifier like this:

 Person newPerson = existingPerson.withAge(30); 
+1
source

Why not make your fields final, and your modifier methods do not 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); } } 
+1
source

Your problem comes down to the following: you want to use a method that safely publishes a virtually immutable, almost, but not quite true copy of an effectively immutable object.

I would go with a solution for the builder: it is verbose, since everyone comes out, but Eclipse helps with this, and it allows all published objects to be virtually immutable. Actual immutability makes secure publishing a no-brainer.

If I wrote this, it would look like this:

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

Depends on how many fields you intend to change. You can create special modified objects, for example:

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

It can also reduce the problem with which you are cloning heavy objects.

0
source

Source: https://habr.com/ru/post/971347/


All Articles