Immutability

Classes

  • This depends on design decisions

  • By using "final" in class signature, you cannot sub class it

  • Make constructor private, use static factory methods

    • If constructor is non default, create a new default private constructor and throw an AssertionException("should not instantiate this class"). This avoids reflection being used to create an instance

  • https://stackoverflow.com/questions/12306651/why-would-one-declare-an-immutable-class-final-in-java

  • https://www.infoworld.com/article/2073003/is-a-java-immutable-class-always-final-.html#:~:text=By%20declaring%20immutable%20classes%20final,immutability%2C%20are%20not%20a%20concern.

Fields

  • Use of private final fields

  • No setter methods

  • Methods that change the state, should be avoided

    • instead return a new object with the new state in constructor

  • But if you have access to the fields via a getter, then the object can stil be mutated internally

    • Thus getter has to either return a copy (deep or shallow copy depending on the object graph)

Arguments

  • Either by convention (to avoid cluttering code), keep all method parameters final.

  • Can use the keyword "final" in method parameters signature

  • Even better would be to make any argument passed into a method be immutable

    • This can be hard, and also lead to trade off in creating less readable code

Variables

Assign once

  • Bad

Thing thing;
if (nextToken == MakeIt) {
  thing = makeTheThing();
} else {
  thing = new SpecialThing(dependencies);
}
thing.doSomethingUseful();
  • Better, use ternary and assign to variable

final var thing =
  nextToken == MakeIt ? makeTheThing(): new SpecialThing(dependencies);
thing.doSomething();
  • Better, wrap it in a function

final var thing = aThingFor(nextToken);
thing.doSomethingUseful();

private Thing aThingFor(Token aToken) {
  return aToken == MakeIt ? makeTheThing() : new SpecialThing(dependencies);
}
  • If used once, can inline, and get rid of variable (as long naming of method is good)

aThingFor(nextToken).doSomethingUseful();

private Thing aThingFor(Token aToken) {
  return aToken == MakeIt ? makeTheThing() : new SpecialThing(dependencies);
}
  • For complex conditions, like switches, can wrap it up in a method

Localise scope

  • Distance between assigning variable and use is far apart

var thing = Thing.DEFAULT;
// lots of code to figure out nextToken
if (nextToken == MakeIt) {
  thing = makeTheThing();
}
thing.doSomethingUseful();
  • Better, use private method

final var thing = theNextThingFrom(aStream);

private Thing theNextThingFrom(Stream aStream) {
// lots of code to figure out nextToken
  if (nextToken == MakeIt) {
    return makeTheThing();
  }
  return Thing.DEFAULT;
}
  • Better use stream

final var thing = nextTokenFrom(aStream)
                    .filter(t -> t == MakeIt)
                    .findFirst()
                    .map(t -> makeTheThing())
                    .orElse(Thing.DEFAULT);

Last updated