I have an immutable class called Employees
like this:
public final class Employees {
private final List<Person> persons;
public Employees() {
persons = new LinkedList<Person>();
}
public List<Person> getPersons() {
return persons;
}
}
How do I keep this class immutable?
I made the field private
and final
, and I didn't provide a setter method. Is this enough to achieve immutability?
If you want to encapsulate a mutable object into an immutable one, then you need to: Create a copy of the mutable object (i.e. via copy constructor, cloning, serialization/deserialization, etc.); never store the reference to the original mutable object. Never return the mutable object.
There are some rules you must follow to achieve immutability in your class: Protect your fields from direct access declaring them as private . Avoid inheritance declaring the class as final or using static factories as a constructor. Make your fields final in order to assign the content only in the declaration.
Answer edited to explain not only the case with a mutable version of Person
, but also with an immutable version of Person
.
Your class is mutable because you can do that:
Employees employees = new Employees();
employees.getPersons().add(new Person());
Note that not passing a list of persons to the constructor if you change your code to create an immutable class you will have a not useful class holding an ever empty list of persons, so I assume that is necessary to pass a List<Person>
to the constructor.
Now there are two scenarios, with different implementations:
Person
is immutablePerson
is mutableScenario 1 - Person
is immutable
You need only to create an immutable copy of the persons parameter in the constructor.
You need also to make final
the class or at least the method getPersons
to be sure that nobody provide a mutable overwritten version of the getPersons
method.
public final class Employees {
private final List<Person> persons;
public Employees(List<Person> persons) {
persons = Collections.unmodifiableList(new ArrayList<>(persons));
}
public List<Person> getPersons() {
return persons;
}
}
Scenario 2 - Person
is mutable
You need to create a deep copy of persons
in the getPersons
method.
You need to create a deep copy of persons
on the constructor.
You need also to make final
the class or at least the method getPersons
to be sure that nobody provide a mutable overwritten version of the getPersons
method.
public final class Employees {
private final List<Person> persons;
public Employees(List<Person> persons) {
persons = new ArrayList<>();
for (Person person : persons) {
persons.add(deepCopy(person)); // If clone is provided
// and creates a deep copy of person
}
}
public List<Person> getPersons() {
List<Person> temp = new ArrayList<>();
for (Person person : persons) {
temp.add(deepCopy(person)); // If clone is provided
// and creates a deep copy of person
}
return temp;
}
public Person deepCopy(Person person) {
Person copy = new Person();
// Provide a deep copy of person
...
return copy;
}
}
This part of the answer is to show why a not deep copy of the personsParameter
passed to the constructor can create mutable versions of Employees
:
List<Person> personsParameter = new ArrayList<>();
Person person = new Person();
person.setName("Pippo");
personsParameter.add(person);
Employees employees = new Employees(personsParameter);
// Prints Pippo
System.out.println(employees.getPersons().get(0).getName());
employees.getPersons().get(0).setName("newName");
// Prints again Pippo
System.out.println(employees.getPersons().get(0).getName());
// But modifiyng something reachable from the parameter
// used in the constructor
person.setName("Pluto");
// Now it prints Pluto, so employees has changed
System.out.println(employees.getPersons().get(0).getName());
No, it is not enough because in java even references are passed by value. So, if your List's
reference escapes ( which will happen, when they call get
), then your class is no longer immutable.
You have 2 choices :
List
and return it when get is called.List
and return it (or replace your original List
with this, then you can return this safely without further wrapping it)Note : You will have to ensure that Person
is either immutable or create defensive copies for each Person
in the List
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