29

I am reading the documentation of Records and don't understand the term "shallowly immutable". What do we mean by shallowly immutable? And if it's immutable why we need a copy constructor? Why two "Hello Worlds!"?

For all record classes, the following invariant must hold: if a record R's components are c1, c2, ... cn, then if a record instance is copied as follows:

 R copy = new R(r.c1(), r.c2(), ..., r.cn());  // copy constructor ?

then it must be the case that r.equals(copy).

Ivo Mori
  • 2,177
  • 5
  • 24
  • 35
Debapriya Biswas
  • 1,079
  • 11
  • 23
  • I have implemented detecting deep immutability for Java Records. The high level is captured in this StackOverflow Answer (and even more thorough treatment is in the Open Source project mentioned there): https://stackoverflow.com/a/75043881/501113 – chaotic3quilibrium Feb 10 '23 at 00:25

2 Answers2

34

Shallowly immutable means, that if a class has fields, these fields are treated as being final. However, their fields (i.e. the fields of the fields) don't need to be final.

You don't need to implement a constructor, it's already implemented this way for you. But if you choose to implement it yourself, e.g. for argument validation, then this invariant should hold.

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
Alex R
  • 3,139
  • 1
  • 18
  • 28
  • 5
    An example for the "shallowly immutability" could be a record instance having an `ArrayList` instance as its field. While that record field cannot refer to any other `ArrayList` list instance, the list's elements can still change (by code holding a reference to that list instance contained in the record). Because the list instance remains mutable even though it's now contained in an immutable record instance – you will only ever get "shallow immutability" from a record. – Ivo Mori May 28 '20 at 06:12
  • 1
    @IvoMori Yes, that's a good example, thank you. Another, non-Record-related example is a call to `Collections.unmodifiableList`: Your cannot change the list but can still change the elements, if they are not immutable. – Alex R May 28 '20 at 06:18
  • On a side note; given the use of the word ___value___ in the Record's documentation "suggests" that _record components_ should be immutable by themselves – otherwise they wouldn't be "values". – Especially when "The implicit declaration of the Object.equals(Object), Object.hashCode(), and Object.toString() methods are derived from all of the component fields." you most likely don't want these methods to depend on mutable data. – Ivo Mori May 28 '20 at 06:44
  • 2
    @IvoMori I severely doubt that's the case, as that significantly limits their usefulness. Nothing is wrong with having those methods depend on mutable data. You just can't use the record type in contexts which require deep immutability (e.g. as `Map` kets). I would argue the word "value" is simply referring to references, not the objects behind them. – HTNW May 28 '20 at 19:12
  • @HTNW Makes sense I was wrongly reading too much into the use of the word _value_ in that context. Thanks for pointing it out. – Ivo Mori May 28 '20 at 22:58
15

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);
    }
}
Michael
  • 41,989
  • 11
  • 82
  • 128