So you might wonder – “Mutable” – what is that all about?
In general we prefer things to be Immutable. With immutability comes improved performance because we do not have to copy things around and the JIT can do some optimizations. And there are less surprises, less WTFs because referenced things doesn’t suddenly change state.
One of my favorites is Optional. It either has a present value or is empty. Even after a few thousand lines of code a reference to an Optional
is either empty or has the value it was initialized with. Love it! No surprises, no WTFs…
It also has a nice stream-like API with map()
and filter()
and whatnot.
Then in java 16 came records
. And I love records. With a few lines of code you define immutable data carriers with sane equals()
, hashCode()
and toString()
implementations and natural named accessors not prefixed with get
as in the JavaBeans legacy. Love it! No surprises, no WTFs…
So whenever I’m going to introduce a class with the primary responsibility of moving some information around I try to use records. Sometimes annotated with lombok @Builder
because it makes construction of these instances somewhat easier and easily split over multiple lines…
And as already mentioned one of the selling points of records is their immutability!
Then recently I was refactoring a rather complex piece of code that dealt with parsing some interesting external files into some rather complex hierarchies and at the bottom sometimes enhancing higher-level items with some information from internal systems. In other words, adding information at a later stage, information not available at construction time.
I believe we’ve all been there…
And I wanted to refactor the code to use records simply because there is much less code to maintain (less TCO) and there are less … surprises…
This phrase seems to come up often… “Less surprises”… Believe me I am getting old and with age comes some resistance towards surprises. I don’t need them and basically want less of them. And compared to how I coded like 25 years ago that mentality has changed my coding style A LOT! So, less surprises, less WTFs are important to me 🙂
My younger co-workers might find me grumpy at times, but with less surprises comes increased maintainability and with that less TCO. So… Lets get on with it!
To solve my hierarchy files parsing issue I needed a TopLevel
type and a Child
type, they have things in common so they implement a HierarchyElement
interface with reference to parent()
which for TopLevel
is it self and for Child
is another child or a TopLevel
. Both has a children()
list and some other stuff. And TopLevel
has a field that can only be set when parsing a deep child and some children might get values from another system later, before we’re done parsing… Got it so far? 😉
One way of doing this would be to – when we have the values for the “late fields” – replace the Child
or the TopLevel
with a copy of the existing with a field or two added…
With a complex hierarchy that is just not trivial. Doable but not trivial and the code would be… Complex and full of surprises. Some junior developer might come along and think WTF – not needed – DELETE… Hmmm…
So I decided to introduce Mutable<T>
. I could have used AtomicReference<T>
but I need no concurrency and has some other needs I might as well cater for.
One thing that really annoyed me in the original implementation was 9 lines of code that basically checked if the top- or the child already had a field set and if not, set the field to some new value. All those ifs-and-buts needs to be unit-tested and then you have some very complex unit-tests to cater for something extremely simple. Must be tested or it does not work but considering TCO…
if (someForeignThing != null) {
if (top.getSomeField() != null) {
top.setSomeField(someForeignThing.getValueForField());
}
if (child.getSomeField() != null) {
child.setSomeField(someForeignThing.getAnotherValue());
}
if (child.getAnotherField() != null) {
child.setAnotherField(someForeignThing.getCompletelyDifferentField());
}
}
So I came up with Mutable<T>
to make it possible to set some rather important fields later. Both the TopLevel
and Child
types already has mutable collections (list of children) that are set to new ArrayList<>()
so having one or two other mutable fields does not seem too arcane.
What should it look like… Well, it must have get()
and set()
like in AtomicReference<T>
. Except I like methods that change a single state of an object to return the previous state so we have T set(T newValue)
.
And since it can supply it’s current value and it can consume a T
, why not have it implement Supplier<T> and Consumer<T>. Supplier uses the method accept()
to set a new value so we delegate that to the set()
method.
And to boil the 9 lines of code down to three we introduce a setIfNull()
method that only set the mutable value if it is already null
and we have:
if (someForeignThing != null) {
top.someField().setIfNull(someForeignThing.getValueForField());
child.someField().setIfNull(someForeignThing.getAnotherValue());
child.anotherField()
.setIfNull(someForeignThing.getCompletelyDifferentField());
}
Less is more! Love it!! And the null
checking only needs to be unit-tested with the Mutable<T>
tests. And I do believe even a novice developer can see what is going on 😉
So the Mutable<T>
has a not-null concept. It looks very much like the Optional
empty or not. But slightly different since the Optional
is immutable so either it has a value present or it is empty. Even the API states this. My mutable has a null or not-null concept, empty is something slightly different and the not-null state can change to another not-null state and even back. So decided to go with a isNull()
method simply because it describes the situation better. Also there is no NoSuchElementException
involved when get()
returns null
😉
We are not trying to solve the infamous NullPointerException
thing here 😀
So the core of it is like the following:
public final class Mutable<T> implements Supplier<T>, Consumer<T> {
private T value;
public boolean isNull() {
return value == null;
}
public boolean setIfNull(T value) {
if (isNull()) {
set(value);
return true;
}
return false;
}
public T set(T value) {
final var old = this.value;
this.value = value;
return old;
}
@Override
public T get() {
return value;
}
@Override
public void accept(T value) {
set(value);
}
@Override
public String toString() {
return String.valueOf(value);
}
}
So get()
, set()
and isNull()
are the very core part of the implementation – the other methods just delegates. Again this makes testing simpler and the JIT can definitely inline those.
The setIfNull()
returns true
if the Mutable
was changed – that information might come in handy sometimes.
There is also a toString()
but that is mostly for debugging purposes and for ensuring a relatively sane output in logs etc.
Things to consider
First, this thing is not thread-safe and is not meant to be. It is for temporary eventually lazy settable items in same-thread situations. If you need thread-safety and concurrency use the AtomicReference<T> and similar. Period 😉
There are no equals()
and hashCode()
and why not?
Basically these methods are for things to be put in collections specifically in Sets and Maps. Since Mutables are … mutable … they should never be put into Sets or used as keys in Maps. Never ever. Remember surprises and WTFs.
One could consider implementing equals()
– it is not difficult to come up with a reasonable implementation, eg:
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
return obj instanceof Mutable<?> other
&& Objects.equals(value, other.value);
}
That would work perfectly fine for all trivial cases. But please do read the JavaDocs for Object.equals():
- It is reflexive: for any non-null reference value
x
,x.equals(x)
should returntrue
. - It is symmetric: for any non-null reference values
x
andy
,x.equals(y)
should returntrue
if and only ify.equals(x)
returnstrue
. - It is transitive: for any non-null reference values
x
,y
, andz
, ifx.equals(y)
returnstrue
andy.equals(z)
returnstrue
, thenx.equals(z)
should returntrue
. - It is consistent: for any non-null reference values
x
andy
, multiple invocations ofx.equals(y)
consistently returntrue
or consistently returnfalse
, provided no information used inequals
comparisons on the objects is modified. - For any non-null reference value
x
,x.equals(null)
should returnfalse
.
The fourth bullet – it is consistent – we cannot ensure that with a Mutable<T>
since after a call to set()
, setIfNull()
or accept()
the method might return something different. The documentation actually allows that, but I hate surprises and things that are equal one moment should still be equal even a few lines of code later…
And another thing is if we implement equals()
we really should also implement hashCode()
(and really consider compareTo()
too but that is another even longer story) with a similar contract and we’re basically in deep trouble. DO NOT. NO NO DO NOT. We hate nasty surprises 😀 We like less WTFs.
By not implementing these methods they revert to the Object
implementations which are based on object identity and are very deterministic. No surprises. We like 😉
Look for nasty surprises here: SurprisesTest.java
Records in Sets and as keys in Maps
So since records are immutable they are perfectly safe to use in Sets and as keys in Maps?
Well not quite. If your implementation contains at least one mutable field (a Mutable<T>
, an AtomicSomething
or some mutable collection) then you are out for nasty surprises. Unless you specifically override equals()
and hashCode()
to be stable towards the mutable elements.
And then while you are at it, consider implementing Comparable<T>
…
So be careful out there, use the Mutable<T>
with care, don’t ever overdo it and please do not ever be tempted to implement equals()
and hashCode()
or use instances in Set or as keys in Maps.