If you consider a class as a composite or hierarchy of other classes and primitives (ints, arrays, etc.), shallow immutability refers to the immutability (constant-ness) of just the first level.
It is in contrast to the term 'deep immutability', which refers to the immutability of the whole hierarchy. Most of the tangible benefits that you hear about immutability, such as implicit thread-safety, apply only to something which is deeply immutable.
Consider this class
class Foo {
private final MutableBar bar;
//ctor, getter
}
This class is shallowly immutable. It cannot be changed directly, but can be changed indirectly, for example
foo.getBar().setSomeProperty(5);
so it is not deeply immutable.
Another example of shallow immutability, using only primitives
class Foo {
private final int[] ints;
Foo(int[] ints) {
this.ints = ints;
}
}
This can be mutated like so
int[] ints = {1};
Foo foo = new Foo(ints);
ints[0] = 2;
For a small hierarchy, it is sometimes simple to make a shallowly immutable class deeply immutable. It usually involves defensive copies, or switching mutable classes to immutable variations.
class Foo {
private final int[] ints;
Foo(int[] ints) {
// copy to protect against the kind of mutation shown above
this.ints = Arrays.copyOf(ints, ints.length);
}
// if you must have a getter for an array, make sure not to return the array itself,
// otherwise the caller can change it.
// for performance reasons, consider an immutable List instead - no copy required
int[] getInts() {
return Arrays.copyOf(ints, ints.length);
}
}