Don't share references to the mutable objects
public class A {
private List<Integer> privateRef;
private int privateVal = 0;
public List<Integer> bad() {
return this.privateRef;
}
public int good() {
return privateVal;
}
}
The bad() method is bad because it exposes a reference to the private member, thus enabling a caller to do this:
A a = new A();
List<Integer> extractedRef = a.bad();
extractedRef.add(1);
Thus mutating a's private list (if you inspect a.privateList, you'll see that it contains 1). A no longer controls its internal state.
The good() method is good because even if a caller does:
A a = new A();
int number = a.good();
number++;
Even though number is 1, the value of a.privateVal remains 0.
Never store references to external, mutable objects passed to the constructor
Imagine we add this constructor to our class:
public class A {
...
public A(List<Integer> anotherList) {
this.privateRef = anotherList;
}
...
}
We're in a similar situation: mutations to the private list may happen externally:
List<Integer> list = new ArrayList<Integer>()
A a = new A(list);
list.add(1);
We have mutated a's internal state (if you inspect a.privateList, you'll see that it contains 1).
if necessary, create copies, and store references to the copies.
That is, if you wish A to be immutable, you should do this:
public A(List<Integer> anotherList) {
this.privateRef = new ArrayList<>(anotherList);
}
This way the new instance of A gets a copy of the list upon creation which becomes part of its internal state. From this point, mutations to the given list ("list" in the example) don't affect a's internal state: only a can mutate its private list.
Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods.
This is how you'd solve the first example, if A wants to expose its internal mutable list it should not do this:
public List<Integer> bad() {
return this.privateRef;
}
but
public List<Integer> better() {
return new ArrayList<>(this.privateRef);
}
At this point
A a = new A();
List<Integer> extractedRef = a.better();
extractedRef.add(1)
At this point, a.privateRef is still empty (so its state is protected from external mutation). But extractedRef will contain 1.
An additional point. Note that even though this class applies all the principles above:
public class A {
private List<Integer> privateRef = new ArrayList<>();
private int privateVal = 0;
public A(List) {
this.privateRef.addAll(this.privateRef);
}
public List<Integer> getList() {
return new ArrayList<>(this.privateRef);
}
public int getVal() {
return privateVal;
}
public void mutate() {
this.privateVal++;
this.privateRef.clear();
}
}
(It doesn't expose references to mutable objects, nor keep references to external mutable objects), it is not really immutable as there is a way to mutate its internal state (call mutate() on it).
You can of course remove mutate(), but a more correct alternative for immutability could be:
public class A {
private final List<Integer> privateRef = new ArrayList<>();
private final int privateVal = 0;
public A(List) {
this.privateRef.addAll(this.privateRef);
}
public List<Integer> getList() {
return new ArrayList<>(this.privateRef);
}
public int getVal() {
return privateVal;
}
}
(I haven't compiled the examples, but they should be almost ok).