Whenever we have tried to learn about functional programming, these two terms have always gained our attention. The first one is pure functions and the second one is immutability. I have tried to explain what are pure functions in one of my previous blogs. In this particular blog, we’ll discuss the practicality of programming with immutability.
What does immutability mean?
The definition that you can see in Wikipedia states that an immutable object is one whose state cannot be modified after it is created. Or in other words, some variable that can’t change its value.
An immutable object is an object whose state cannot be modified after it is created. In multi-threaded applications that is a great thing. It allows for a thread to act on data represented by immutable objects without worrying what other threads are up to.
Shared state is fine if it is immutable. Mutable state is fine if it is not shared.
Shared mutability is where a piece of code mutates variables or objects outside its immediate local scope. We’re so used to shared mutability that it may appear impossible to implement some logic without mutating variables. But, it’s really not hard.
Let’s take a look at an example.
Let’s start with a Student
class with a few properties including age
.
public class Student {
private final String name;
private final int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
First, here’s the code to create a list of friends:
public class Sample {
public static void main(String[] args) {
List<Student> friends = Arrays.asList(
new Student("Neha", 11),
new Student("Priya", 21),
new Student("Ajay", 11),
new Student("Mohit", 23));
}
}
Now, let’s compute the total age of all people among the friends
collection.
int totalAge = 0;
for(Student friend : friends) {
totalAge += friend.getAge();
}
System.out.println("Total age of friends: " + totalAge);
We first initialized a variable named totalAge with a value of 0. Then, using the imperative style loop, that is an external iterator, we get one student at a time, get their age, and add it to the totalAge variable. The += is where the mutation is actually happening.
Why is mutability not a good thing? Here are a few reasons:
- The code suffers from primitive obsession. We need to take care of low-level details: get the value from the student, add to the total, set it back into the variable. Changing the variable from one value to another is our responsibility here.
- The variable totalAge is often referred to as a garbage variable. It’s there to hold the temporary transitional state through the loop. It adds to the noise in the code.
- Mutability leads to more bugs. More mutable a piece of code is, the higher are the chances of bugs. Mutability introduces moving parts, and since they are hard to reason, it’s easier to make mistakes with state transitions.
- Making code execution in parallel becomes difficult. Suppose the computation for each element were expensive and we want to speed up the computation by introducing multiple threads. Creating threads is not that hard, but we’ll quickly run into thread safety issues due to the mutable variable. Shared mutability means lack of thread-safety. In order to make this code thread-safe we have to put locks or synchronization primitives around the change to the mutable variable. This introduces accidental complexity in code, makes it harder to understand, and leads to more bugs as well—it’s the start of a vicious cycle.
We should avoid shared mutability as much as possible. Let’s try to solve the above issue with the functional approach:
System.out.println("Total age of friends: " + friends.stream() .mapToInt(Student::getAge) .sum());
The code here seems more concise, expressive with less moving parts. It follows immutability.
How is this better than the previous imperative style code with mutability?
- We did not spend any effort telling the code how to update any variable’s values. Particularly, the code tells what and not how. We ask for all the age values to be summed. We did not have to tell how to get each element, what to add, where to put the result, etc.
- There is no garbage variable.
- Less code, fewer moving parts, fewer bugs. Easier to trace what’s going on.
- Easier to parallelize. Since the code is not performing any explicit mutation, there’s nothing to protect for thread-safety. So, it’s easier to parallelize. Immutability for the win.
In Java, we do not have immutability implemented by default. However, we can leverage immutability in the following ways:
Immutable collection:
List<String> stringList = Arrays.asList("a", "b", "c"); stringList = Collections.unmodifiableList(stringList);//immutable list
Set<String> stringSet = new HashSet<>(Arrays.asList("a", "b", "c")); stringSet = Collections.unmodifiableSet(stringSet);//immutable set
Map<String, Integer> stringMap = new HashMap<String, Integer>(); stringMap.put("a", 1); stringMap.put("b", 2); stringMap.put("c", 3); stringMap = Collections.unmodifiableMap(stringMap);//immutable map
Immutable class:
Following are the requirements for a class to be immutable:
• The class must be declared as final.
• Data members in the class must be declared as final (So that we can’t change the value of it after object creation)
• A parameterized constructor
• Getter method for all the variables in it
• No setters
public final class Student { final String name; final int regNo; public Student(String name, int regNo) { this.name = name; this.regNo = regNo; } public String getName() { return name; } public int getRegNo() { return regNo; } }
The bottom line is, make sure your data type isn’t exposed, unchangeable, and can be viewed only.
I hope, you have liked my blog. If you have any doubt or any suggestions to make please drop a comment. Thanks!
References:
Immutability by Venkat Subramaniam
I do functional Swift these days, and haven’t yet really used Java but loved your positive summary of why not functional!! Exactly!