Is it truly immutable in all senses and situations? No. Is it immutable to a certain degree? Yes.
As long as you're doing only variable assignment, the immutable dog is indeed always going to remain unchanged. This is generally due to the pass-by-value semantics, which is greatly explained here.
When you copy the reference of the color
in the ImmutableDog
dog, you essentially copy the handle. When you then modify the color through the mutable dog by assigning the Pink/Black
value to it, the handle of the mutable dog changes, but immutable dog still holds the original handle, pointing to original color.
With the String
type, this goes a step further, since strings are immutable. Therefore calling any method on the string is guaranteed not to modify the original value. So in terms of string, yes, the immutable dog is truly immutable and can be trusted.
Collections DO make a difference. If we change the design of your classes slightly:
public class MutableDog {
public String name;
public List<String> acceptedMeals;
public MutableDog(String name, List<String> acceptedMeals) {
this.name = name;
this.acceptedMeals = new ArrayList<>(acceptedMeals);
}
}
public class ImmutableDog {
private final String name;
private final Iterable<String> acceptedMeals;
public ImmutableDog(MutableDog doggy) {
this.name = doggy.name;
this.acceptedMeals = doggy.acceptedMeals;
}
public Iterable<String> getAcceptedMeals() {
return this.acceptedMeals;
}
}
and execute the following code:
public static void main(String[] args) throws IOException {
MutableDog mutableDog = new MutableDog("Spot", Collections.singletonList("Chicken"));
ImmutableDog immutableDog = new ImmutableDog(mutableDog);
immutableDog.getAcceptedMeals().forEach(System.out::println);
mutableDog.acceptedMeals.add("Pasta");
immutableDog.getAcceptedMeals().forEach(System.out::println);
}
the following is printed out:
Chicken
Chicken
Pasta
This is due to the ImmutableDog
's constructor copying handle to the acceptedMeals
collection, but the new handle points to the same location as the original collection. So when you call modification through the mutable dog, since the ImmutableDog
points to the same place in memory, its values are also presumably modified.
In this specific case, you could circumvent this side effect by performing a deep copy of the collection, rather than simply copying the reference handle:
public ImmutableDog(MutableDog doggy) {
this.name = doggy.name;
this.acceptedMeals = new ArrayList<>(doggy.acceptedMeals);
}
By doing this, the same main
method presented above prints only the following:
Chicken
Chicken