1
votes

I'm new to Java and is trying to learn strategy for defining immutable objects via Java tutorial Oracle. Due to my limited knowledge and probably how it was worded, I'm really struggling to understand the paragraph below, I was just wondering if anyone could kindly explain to me what it actually means. Thanks in advance for any help!

Don't share references to the mutable objects. Never store references to external, mutable objects passed to the constructor; if necessary, create copies, and store references to the copies. Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods.

4

4 Answers

2
votes

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).

0
votes

Practically it says you don't want to expose your state to outside world, so outside world could modify your internal state as it wants. You don't want this because it's your business in that internal state, and someone could alter it. That's also why it recomands to create copies of you internal mutable object and pass them outside as copies. They will alter those copies, but will not have side effect on your internal state.

0
votes

Example:

/**
 * A simple mutable.
 */
class Mutable {

    private int n;

    public Mutable(int n) {
        this.n = n;
    }

    public int getN() {
        return n;
    }

    public void setN(int n) {
        this.n = n;
    }

    @Override
    public String toString() {
        return "Mutable{" + "n=" + n + '}';
    }

}

// A thing that holds things.
class Thing<T> {

    T m;

    public Thing(T m) {
        this.m = m;
    }

    public T getM() {
        return m;
    }

    @Override
    public String toString() {
        return "Thing{" + "m=" + m + '}';
    }

}

public void test() {
    // Never store references to external, mutable objects passed to the constructor
    Mutable m = new Mutable(10);
    // t10 holds a reference to my mutable `m`, currently containing `10`
    Thing<Mutable> t10 = new Thing<>(m);
    // Now `m` holds `50`, even the one in `t10`.
    m.setN(50);
    // Make the new one holding `m` at value `50` now.
    Thing<Mutable> t50 = new Thing<>(m);
    // Both print the same because they both hold the same `m`
    System.out.println("t10 = " + t10 + " t50 = " + t50);
    // We can even mess with it after the fact - this is why you should return a copy.
    t50.getM().setN(42);
    // Both print the same because they both hold the same `m`
    System.out.println("t10 = " + t10 + " t50 = " + t50);
}

This demmonstrates all three of the points made.

  • If you pass a mutable to a constructor and hold that mutable in the state, a change to that mutable will change the state of the object.

  • If you return a mutable it can also be modified outside your object, thus changing your state.

To avoid this:

  • If possible, use immutable in all cases.
  • If you must use a mutable in your constructor, take a copy of it if possible.
  • If you return the mutable anywhere, return a copy if possible.
0
votes

While creating Immutable object, if there are mutable fields like Date object then following lines applies:

Don't share references to the mutable objects. Never store references to external, mutable objects passed to the constructor; if necessary, create copies, and store references to the copies. Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods.

The same can be achieved as follows :

  • Create a defensive copy of the Date object while creating immutable object.
  • Do not provide any setter methods for this Date Object.
  • If at all you need to provide getter method then, getter method should return a copy of the date field since Date itself is mutable. Here, clone() method helps to achieve this.

Now, Since there is no reference to the Date field in the class, any outside this class cannot modify the state of Immutable class.

Check Example for Immutable class