Effective Java Item 17 lists five rules. Miss any one of them and the class is mutable in some sneaky way that will eventually bite you.
The five rules
- Don't provide methods that modify state. No setters, no
add/remove, novoidmutators. - Ensure the class can't be extended. Mark it
final, or make the constructor private and expose static factories. - Make all fields
final. Single assignment, plus the JMM safe-publication guarantee for properly constructed objects. - Make all fields
private. Even withfinal, apublic final Dateleaks the referent so the caller can mutate it. - Ensure exclusive access to mutable components. Defensive copy on the way in (constructor) and on the way out (accessor).
Period with every rule applied
public final class Period { // Rule 2: final
private final Date start; // Rules 3 + 4: private final
private final Date end;
public Period(Date start, Date end) {
this.start = new Date(start.getTime()); // Rule 5: copy IN
this.end = new Date(end.getTime());
if (this.start.after(this.end)) // validate AFTER copy
throw new IllegalArgumentException(start + " after " + end);
}
public Date start() { return new Date(start.getTime()); } // Rule 5: copy OUT
public Date end() { return new Date(end.getTime()); }
// Rule 1: no setStart, no setEnd, no shift(long delta)
}
What each rule actually buys you
A "mostly immutable" class with one forgotten rule is worse than a frankly mutable one — the rest of your codebase will trust the immutability you advertised and skip locks or defensive copies of its own.