Immutable Vs Mutable Objects
As a programmer you will often come across the terms Immutable and Mutable. In this blog post, I’m going to go explain what these terms mean and show some examples of an immutable object from my code.
A closer look at Immutable and Mutable objects
An object whose state cannot be modified once is has been created.
An example of this in Java would be a constant which is a variable that cannot be changed once you have set it’s value.
Java:String DOG = "Dog"
Weirdly, in Ruby even though you can create constants, they do not strictly behave as constants and are still mutable:
Ruby:DOG = "Dog"irb(main):001:0> DOG = "dog"
=> "dog"irb(main):002:0> DOG
=> "dog"irb(main):003:0> DOG = "cat"(irb):3: warning: already initialized constant DOG
(irb):1: warning: previous definition of DOG was here
=> "cat"irb(main):004:0> DOG
=> "cat"
In the example above, I can reassign the value of the constant DOG and doing so will mutate and therefore change the “constant”. The only thing that happens is that a warning is displayed saying that the previous constant has changed.
In Java you can also declare variables or classes as final which is another way of saying that the object or its values won’t change.
public final String dog = "Dog"Ruby has an enumerable method .freeze which stops the object being modified and displays an error when you try to modify it using .concat or << (append). This is because both these methods try to mutate the frozen object by appending an additional string to it. There is no way to unfreeze a frozen object so at that point it is truly immutable.
irb(main):001:0> dog = "dog"
=> "dog"irb(main):002:0> dog.object_id
=> 70310733405180irb(main):003:0> dog.concat("!")
=> "dog!"irb(main):004:0> dog.freeze
=> "dog!"irb(main):005:0> dog.concat("!!")
Traceback (most recent call last):
3: from /usr/local/bin/irb:11:in `<main>'
2: from (irb):5
1: from (irb):5:in `concat'
FrozenError (can't modify frozen String)irb(main):006:0> dog.object_id
=> 70310733405180
// Below, even though I use dog which is frozen, by using "+", I
// create a new object but as I don't assign it to a variable this // is lost. When we call dog again it's still go the original value // and ID it was assigned.irb(main):007:0> dog + "!!"
=> "dog!!!"irb(main):009:0> dog
=> "dog!"irb(main):010:0> dog.object_id
=> 70310733405180
*One interesting thing to note is that concatenation or appending is not the same as using the + method in Ruby. I was trying to test .freeze with adding two strings together and I was able to use it without getting an error message.
After reading the ruby documentation on what the + method does I realised it creates a new String object containing the previous string which is why no error came up and I was able to use the dog variable in my updated string.
You can call .frozen? on an object that you have used the .freeze method on to check its status and whether if an object’s state can be modified or not.
irb(main):011:0> dog.frozen?
=> trueImmutable state in my own code
I built a game of Tic Tac Toe in Java and throughout the codebase I was using the grid instance variable that lived on my Board class and manipulating it to update the grid or check a value on the grid.
Get Katerina’s stories in your inbox
Join Medium for free to get updates from this writer.
Each time it was a player’s turn to make a move currentPlayer.playMove(board) was called.
public class Game { public void run() {
// I have omitted code that's unnecessary for this example. while (gameIsNotOver()) {
ui.askForMove(currentPlayer.getMark(), this.board.grid);
currentPlayer.playMove(board);
displayBoard();
switchPlayer(playerOne, playerTwo);
}
}}
public class Computer { public void playMove(Board board) {
int playerMove = move(board);
board.updateMove(playerMove, mark);
} public int move(Board board) {
List<Integer> possibleMoves = board.availableMoves();
return possibleMoves.get(0);
}}public class HumanPlayer { public void playMove(Board board) {
int playerMove = getMove(board);
board.updateMove(playerMove, this.mark);
} private Integer getMove(Board board) {
String userMove = ui.getMove(board);
return Integer.parseInt(String.valueOf(userMove)) - 1;
}}public class Board { public List<Mark> updateMove(int position, Mark mark) {
this.grid.set(position, mark);
return this.grid;
}}
I wanted to remove the mutable state from the Board class so the way I did this was by creating a new instance of the Board class each time the grid was modified then instantiating it with the updated version of the grid.
When a new instance of the Board is being created fresh then passed back to the game it means objects don’t interfere with each other’s copies of the Board and the Game class’s version of the board remains consistent. Or, as Roger details in this blog post here, “ If one thread needs a new version or altered version of that object, it must create a new one therefore any other threads will still have the original object.”
In the code example above, if there was a mistake and both players made a move on the Board’s grid one after the other using the same state of the grid, it’s clear to see how this could end up causing a lot of issues. One player would end up having more than one turn and the rest of the grid would be ruined as a result of this. However, as the playMove() method allows the player to select a move and update the grid in the same method, it somewhat removes this issue.
Though that was the case, I still wanted to make the Board truly immutable and ensure a the state of an object a player has is guaranteed to be correct.
After changes:
public class Game { public void run() { // I have omitted code that's unnecessary for this example. while (gameIsNotOver()) {
ui.askForMove(currentPlayer.getMark(), this.board.grid);
board = currentPlayer.playMove(board);
displayBoard();
switchPlayer(playerOne, playerTwo);
}
}
}public class Computer { public Board playMove(Board board) {
int playerMove = move(board);
return board.updateMove(playerMove, mark);
}}public class HumanPlayer { public Board playMove(Board board) {
int playerMove = getMove(board);
return board.updateMove(playerMove, this.mark);
}}public class Board { public Board updateMove(int position, Mark mark) {
this.grid.set(position, mark);
return new Board(size, this.grid);
}}
Instead of returning a updated grid each time a player made a move, I returned a new instance of the Board class and instantiated it with the updated grid. This meant the Board was never truly modified as each time it would just be a new Board class with the current state of the grid.




