The topic of Mutability has been abscent from programming textbooks
Big Mistake!
If you don't control the mutability in your code, you expose yourself to really bad things!
What was the last sentence said before Skynet took over humanity?
"I would make this
class mutable, what
could possibly go wrong?"
We know what happened next...
If NPE has caused
losses of billion of dollars, surely mutability has caused losses around
that magnitude!
Is not that people is
not aware of it!
Item 15: Minimize mutability
First edition was
written in 2001!
So let's make 2016 the
year in which mutability gains the same importance as encapsulation
Let's begin with
a warmup
What's a reference?
It's a variable which
allows the indirect
manipulation of objects
You can really think of
it as a variable that holds
the memory address
of an object
What's the state
of an Object?
The values of its attributes
What's an argument?
It's the value of a parameter
used to call a method
public class Main {
static void times2(int n) {
n *= 2;
}
public static void main(String... args) {
int n = 1;
times2(n);
System.out.println(n);
}
}
What does it print?
It prints "1"
Why?
- Java has "pass-by-value"
- It means that a copy of the arguments
is made when executing a method. - The "n" inside times2() is a different
variable from the "n" in main(), but have
the same value at the time the method is called. - The modification inside times2() doesn't affects the argument.
public class Main {
int n = 1;
static void times2(Main obj) {
obj.n *= 2;
}
public static void main(String... args) {
Main obj = new Main();
times2(obj);
System.out.println(obj.n);
}
}
What does it print?
It prints "2"
Why?
- Parameter of times2() is now a reference
- When creating obj, we create an object and get a reference to it
- When calling times2() a copy of the argument is made: now we have 2 reference to the same object
- "obj.n" modifies the state of the object which is accesible from 2 references!
- After the method executes, we are left with one reference to the object, which
was modified inside times2()
No! Java doesn't
have pass-by-reference
for Objects!
It only has pass-by-value, but since objects can only be used with references
it is similar to
pass-by-reference
in other languages
public class Main {
int n = 1;
static void times2(Main obj) {
obj = new Main();
obj.n *= 2;
}
public static void main(String... args) {
Main obj = new Main();
times2(obj);
System.out.println(obj.n);
}
}
What does it print?
It prints "1"
Why?
- As soon as times2() is in the stack of execution,
we have 2 reference to the same object - In the first line of code, we put the reference
in the method to refer to a new object. Now
we have 2 reference pointing to different objects. - Changes in the state of the object inside the
method are not related to the object outside of it.
Why do we
have references?
Mainly because it makes it cheap to copy arguments when calling methods!
How much memory
does a reference use?
Depends on the OS, typically
32-bits or 64-bits
How much memory
does an object use?
It varies from JVM to JVM but beside the size of the attributes there is usually a cost in memory associated to overhead and padding, usually 12 bytes
Since objects can contain objects, the total size of an object can be big
It would use a lot
of memory and take
a lot of time to
copy objects when
calling methods
References has other niceties like Polymorphism but memory usage was surely the first reason to have them
Going back to mutability...
When you use a reference as argument you don't know if the method will change its state!
You can go from this...
...to this
And it may take a while
until you notice
You have to be specially careful in multi-threaded environments where you depend on data sharing
Why is immutability
desireable?
They are easy
to reason about
When you see a reference there is only one possible state for it, the one with which was constructed
You know exactly what's
the state of the arguments
after calling a method
So how do we make
immutable objects?
Class Design revisited
We want to model
a triangle, not in the Analytic Geometry sense but the "classical" sense:
3 sides and 3 angles
Triangle
- We need to be able to construct a
triangle knowing its 3 sides - We need to know if two triangles are
equal and have a String representation
public class Triangle {
private Sides[] sides = new Sides[3];
public Triangle() {
}
public Side getA() { return sides[0]; }
public Side getB() { return sides[1]; }
public Side getC() { return sides[2]; }
public void setA(Side a) { this.a = sides[0]; }
public void setB(Side b) { this.b = sides[1]; }
public void setC(Side c) { this.c = sides[2]; }
public boolean equals(Object obj) {
if (obj == null) { return false; }
if (obj == this) { return true; }
if (!(obj instanceof Triangle)) { return false; }
return Arrays.equals(sides, ((Triangle) obj).sides));
}
}
That class has a lot of implementation that is not needed!
YAGNI
You Aren't Gonna Need It
YAGNI
- Don't implement functionality
until needed - You may waste time on something
that doesn't add value to your client - You may create a problem where
it wasn't one
Just as is good practice to
make attributes as less visible
as possible, is also to create classes as immutable as possible
Where does
the idea of
setters-for-everything
come?!
Java Beans
What's a Java Bean
- A class with an empty constructor
- With getters and setters for each
property that they have
Why do we need
Java Beans?
It's a protocol so that FW's can create objects from sources like a file or DB
So when we write
@Entity
@Table(name = "foos")
public class Foo implements java.io.Serializable {
private String code;
// remember this is unnecessary if there is no other constructor
public Foo(){}
@Column(name = "CODE")
public String getCode() {
return this.code;
}
public void setSCode(String Code) {
this.code = code;
}
}
How easy is for a FW to create Foo's objects
- With the name of the class they can
get an instance with reflection - They can call an empty constructor,
no need to worry about the order of parameters - For a property xyz, they can set it values calling,
again by reflection, a method called "set" + xyz - For a property xyz, they can expose it calling by reflection, a method called "get" + xyz
When Java was getting traction, this protocol was really important so that tools such as JSF and Hibernate could blossom!
But a Java Bean is only useful when
a FW needs it, so
for everything else we can have immutable classes
Beware of classes with
only empty constructors, they seem innocent but lead to mutability!
Only use Java Beans when a FW
demands it!
Recipe to immutability
1. Make all your attributes final
2. Don't provide mutators e.g. setters
3. If you use
as a parameter a mutable class, don't use a simple assignment, copy
its value
public class Foo {
private final Date date;
public Foo(Date date) {
this.date = date; // DON'T
}
// more code
}
public class Foo {
private final Date date;
public Foo(Date date) {
this.date = new Date(date.getTime()); // DO
}
// more code
}
public class Foo {
private final LocalDateTime date;
public Foo(LocalDateTime date) {
this.date = date; // Is this OK?
}
// more code
}
Ok, since LocalDateTime
is immutable
4. Don't return references to your mutable attributes
public class Bar {
private List<String> xyz;
// code
public List<String> getXyz() {
return xyz; // DON'T
}
}
public class Bar {
private List<String> xyz;
// code
public List<String> getXyz() {
return new ArrayList<>(xyz); // DO
}
}
public class Bar {
private List<String> xyz;
// code
public List<String> getXyz() {
return Collection.ummodifiableList(xyz); // DO
}
}
I like this better
Following those 4 steps create weak immutability
5. Avoid subclassing
That fifth step will give
you Strong Immutability
Avoid subclassing
-
Make class final
- Have private constructor(s)
and use static factories to
create objects
String, Wrapper Classes, Classes on java.time, etc. have Strong Immutability
BigInteger, BigDecimal,
File, etc. have
Weak Immutability
Weak Immutability
is enough for most
of our projects...
...but is also true
that most of our
classes aren't meant
to be extended
Mutators
A Mutator should
return a new
object with the
updated state
public final class Fraction {
public final int denominator;
public final int numerator;
public Fraction(int d, int n) {
final int GCF = gcf(d, n);
this.denominator = d / GCF;
this.numerator = n / GCF;
}
public Fraction plus(final Fraction that) {
final int LCM = lcm(numerator, that.numerator);
final int denominator = LCM / this.numerator * this.denominator
+ LCM / that.numerator * that.denominator;
return new Fraction(denominator, LCM);
}
}
Returning a new
object also mean you
can chain operations
public Fraction plus(final Fraction that) {
final int LCM = lcm(numerator, that.numerator);
final int denominator = LCM / this.numerator * this.denominator
+ LCM / that.numerator * that.denominator;
return new Fraction(denominator, LCM);
}
public Fraction opposite() {
return new Fraction(-denominator, numerator);
}
public Fraction minus(final Fraction that) {
return this.plus(that.opposite());
}
Wait a moment!
You are creating a lot of objects,
that's a heavy memory footprint!
Programming is always about compromises
Mutable: fast, less memory
Immutable: safer
Computers have
come a long way,
so velocity and memory
are usually no longer
an issue
But there are
strategies that help
with memory usage
public enum Cell {
ALIVE {
public Cell next(int aliveNeighbors) {
if (aliveNeighbors < 2 || aliveNeighbors > 3) {
return DEAD;
}
return this;
}
},
DEAD {
public Cell next(int aliveNeighbors) {
if (aliveNeighbors == 3) {
return ALIVE;
}
return this;
}
};
public abstract Cell next(int aliveNeighbors);
}
Is this efficient?
It is; return Singletons
or objects from a
cache or object pool
public final class Document {
private final String title;
private final String text;
private final LocalDate dateOfPublish;
// more code
public Document withTitle(String newTitle) {
return new Document(newTitle, this.text, this.dateOfPublish);
}
}
Is this efficient?
Two objects can share some of
its structure to minimize memory footprint
If they share immutable objects there wouldn't
be undesirable surprises
That's the principle of the String Pool
in the JVM
LocalDateTime thePast = timePoint
.withDayOfMonth(10)
.withYear(2010);
and Java 8 Time API
Make mutators return a new object, that share as much (immutable) structure as possible
Mutability in
the inside
Mutability isn't bad per se, sharing mutability is!
You can use mutability for implementation details
public final class Board {
private final Cell[][] board;
private final int columns;
private final int rows;
private Board(Builder builder) {
board = builder.board;
columns = builder.columns;
rows = builder.rows;
}
public static class Builder {
private Cell[][] board;
private int columns;
private int rows;
public Builder(int columns, int rows) {
this.columns = columns;
this.rows = rows;
board = new Cell[columns][rows];
for (int i = 0; i < columns; ++i) {
Arrays.fill(board[i], Cell.DEAD);
}
}
public Builder aliveCellIn(int column, int row) {
board[column][row] = Cell.ALIVE;
return this;
}
public Board build() {
return new Board(this);
}
}
}
Mutability stays inside
the Builder class,
but the clients are
not exposed to it
Board board = new Board.Builder(4, 4)
.aliveCellIn(1, 1)
.aliveCellIn(1, 2)
.aliveCellIn(2, 1)
.aliveCellIn(2, 2)
.build();
public final class Board {
private final Cell[][] board;
private final int columns;
private final int rows;
// Code
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < columns; ++i) {
for (int j = 0; j < rows; ++j) {
builder.append(board[i][j]);
}
builder.append(String.format("%n"));
}
return builder.toString();
}
}
Again, mutability
stays inside
the method
public final class Board {
private final Cell[][] board;
private final int columns;
private final int rows;
private static final int[] dx = {-1,-1,-1, 0, 0, 1, 1, 1};
private static final int[] dy = {-1, 0, 1,-1, 1,-1, 0, 1};
private static final int NEIGHBORS = dx.length;
private Board(Board copy) {
columns = copy.columns;
rows = copy.rows;
this.board = new Cell[columns][rows];
for (int i = 0; i < columns; ++i) {
this.board[i] = Arrays.copyOf(copy.board[i], rows);
}
}
// Code
}
public final class Board {
// Code
private boolean valid(int column, int row) {
return column >= 0 && column < columns && row >= 0 && row < rows;
}
private int countAliveNeighbors(int column, int row) {
int total = 0;
for (int i = 0; i < NEIGHBORS; ++i) {
int nx = column + dx[i];
int ny = row + dy[i];
if (valid(nx, ny)) {
total += board[nx][ny] == Cell.ALIVE ? 1 : 0;
}
}
return total;
}
public Board next() {
Board copy = new Board(this);
for (int i = 0; i < columns; ++i) {
for (int j = 0; j < rows; ++j) {
int alive = countAliveNeighbors(i, j);
copy.board[i][j] = board[i][j].next(alive);
}
}
return copy;
}
// Code
}
Mutability
in the inside!
Don't fear mutability,
but don't share it!
Data Structures
Great! I'm convinced
on minimizing mutability, but how I deal
with Collections?
Collections by definition are mutable!
and Collections by definition must
be efficient!
Remember, if mutability stays in the inside
there's no harm done!
public final class Foo {
private final List<String> list;
public Foo(final List<String> list) {
this.list = new ArrayList<>(Objects.requireNonNull(list));
}
public List<String> getList() {
return Collections.unmodifiableList(list);
}
}
Collections as implementation details
- Create a new collection, all Java
classes have copy-constructors - Return an unmodifiable view of the collection;
there is Collections.unmodifiableList(),
Collections.unmodifiableSet(),
Collections.unmodifiableMap()
All functional languages have immutable data structures that use structure sharing
In Java, Guava
has them!
Java API has specialized data structures for
multi-threading appilcations
Other Tips
Use StringBuilder
and String.format()
Make all your
parameters final
Have you ever thought
of how silly it is to
mutate a parameter?
(Some) Functional languages have parameters immutable by default
public final class Document {
private final String title;
private final String document;
private final String text;
public Document(final String title, final String document, final String text) {
this.title = Objects.requireNonNull(title);
this.document = Objects.requireNonNull(document);
this.text = Objects.requireNonNull(text);
}
}
It is a little verbose!
But worth it
Use
- setFoo(Foo foo) for typical setters
- withFoo(Foo foo) to return a new object
with the state but with updated value foo - foo(Foo foo) as getters of an inner
Builder class to create an immutable object
If you use immutability
and have a performance hit, first use a profiler to verify the source of the problem before changing to mutability
Let's make a vow
I vow, to the best
of my possibilities
- to let mutability live only "in the inside"
- to don't share mutable state
- to explain to others why those ideas are important
Q&A
Thanks!
@gaijinco
gaijinco@gmail.com
Mutable-Types: Apocalypses
By Carlos Obregón
Mutable-Types: Apocalypses
Best practices on how to minimize mutability in your code
- 1,924