Here is a Java program that implements a simple counter.

class Counter
{
    private int count;
    
    public Counter() {
        this.count = 0;
    }
    
    public int getCount() {
        return this.count;
    }
        
    public boolean isLessThan(int n) {
        return this.count < n;
    }

    public void increment() {
        this.count += 1;
    }
}

public class Main 
{
    public static void main(String[] args) 
    {
        Counter counter = new Counter();
        
        while (counter.isLessThan(10)) 
        {
            System.out.println(counter.getCount());
            counter.increment();
        }
    }
}

In the following refactored version of the same program, we have instead replaced Counter with a class called ImmutableCounter. Note that we added the final keyword to the count field to make the class immutable. In Java, a field declared final cannot be changed after object instantiation.

final class ImmutableCounter
{
    private final int count;
    
    public ImmutableCounter(int count) {
        this.count = count;
    }
    
    public static ImmutableCounter zero() {
        return new ImmutableCounter(0);
    }
    
    public int getCount() {
        return this.count;
    }
    
    public boolean isLessThan(int n) {
        return this.count < n;
    }

    /* -------------------------------------------- */    
    /* 1. Implement method for incrementing counter */
    /* -------------------------------------------- */    
}   

public class Main 
{
    public static void main(String[] args) 
    {
        ImmutableCounter counter = ImmutableCounter.zero();
        
        while (counter.isLessThan(10)) {
            System.out.println(counter.getCount());
            
            /* ------------------------- */
            /* 2. Increment counter here */
            /* ------------------------- */
        }
    }
}

Question 1:

We can’t modify the counter object, so how would you write the two missing pieces of code, assuming the rest of the program remains the same? Hint: Immutable data structures favor construction over mutation. Look at the zero method for a clue.

Question 2:

What are some advantages of programming with immutable data and implicit state? (For instance, why are Java strings immutable?) Are there any disadvantages? I guess that was more than one question :)

Toggle answer

Solution:

Question 1

public ImmutableCounter next() {
      return new ImmutableCounter(this.count + 1);
}

counter = counter.next();

Question 2

Immutability eliminates side-effects, which makes code easier to reason about and to test. Immutable objects are inherently thread-safe. A disadvantage is that it can be more difficult to write time- and resource-efficient code using immutable data.