Using some setters, without setting all needed properties in the constructor(s)
public final class Person { // example of a bad immutability
private final String name;
private final String surname;
public Person(String name) {
this.name = name;
}
public String getName() { return name;}
public String getSurname() { return surname;}
public void setSurname(String surname) { this.surname = surname); }
}
It’s easy to show that Person
class is not immutable:
Person person = new Person("Joe");
person.setSurname("Average"); // NOT OK, change surname field after creation
To fix it, simply delete setSurname()
and refactor the constructor as follows:
public Person(String name, String surname) {
this.name = name;
this.surname = surname;
}
Not marking instance variables as private and final
Take a look at the following class:
public final class Person {
public String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
The following snippet shows that the above class is not immutable:
Person person = new Person("Average Joe");
person.name = "Magic Mike"; // not OK, new name for person after creation
To fix it, simply mark name property as private
and final
.
Exposing a mutable object of the class in a getter
Take a look at the following class:
import java.util.List;
import java.util.ArrayList;
public final class Names {
private final List<String> names;
public Names(List<String> names) {
this.names = new ArrayList<String>(names);
}
public List<String> getNames() {
return names;
}
public int size() {
return names.size();
}
}
Names
class seems immutable at the first sight, but it is not as the following code shows:
List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
Names names = new Names(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList = names.getNames();
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"
This happened because a change to the reference List returned by getNames()
can modify the actual list of Names
.
To fix this, simply avoid returning references that reference class's mutable objects either by making defensive copies, as follows:
public List<String> getNames() {
return new ArrayList<String>(this.names); // copies elements
}
or by designing getters in way that only other immutable objects and primitives are returned, as follows:
public String getName(int index) {
return names.get(index);
}
public int size() {
return names.size();
}
Injecting constructor with object(s) that can be modified outside the immutable class
This is a variation of the previous flaw. Take a look at the following class:
import java.util.List;
public final class NewNames {
private final List<String> names;
public Names(List<String> names) {
this.names = names;
}
public String getName(int index) {
return names.get(index);
}
public int size() {
return names.size();
}
}
As Names
class before, also NewNames
class seems immutable at the first sight, but it is not, in fact the following snippet proves the contrary:
List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
NewNames names = new NewNames(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"
To fix this, as in the previous flaw, simply make defensive copies of the object without assigning it directly to the immutable class, i.e. constructor can be changed as follows:
public Names(List<String> names) {
this.names = new ArrayList<String>(names);
}
Letting the methods of the class being overridden
Take a look at the following class:
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() { return name;}
}
Person
class seems immutable at the first sight, but suppose a new subclass of Person
is defined:
public class MutablePerson extends Person {
private String newName;
public MutablePerson(String name) {
super(name);
}
@Override
public String getName() {
return newName;
}
public void setName(String name) {
newName = name;
}
}
now Person
(im)mutability can be exploited through polymorphism by using the new subclass:
Person person = new MutablePerson("Average Joe");
System.out.println(person.getName()); prints Average Joe
person.setName("Magic Mike"); // NOT OK, person has now a new name!
System.out.println(person.getName()); // prints Magic Mike
To fix this, either mark the class as final
so it cannot be extended or declare all of its constructor(s) as private
.