Immutable

  • An object whose value/state cannot be changed after creation
  • It is a guarantee that nothing can change the object
  • Immutable objects are always thread safe
  • An object is immutable if
    • its state cannot be changed after construction
    • all its fields are finalnon-final are also possible but care must be taken, since JMM guarantees that final fields are Safely Initialized
    • it is Properly Constructed
  • Examples:
    • Objects of wrapper classes like Integer
    • String objects

Effectively Immutable

  • Objects that are not technically immutable, but whose state will not be modified after publication, are called effectively immutable
  • Safely Published effectively immutable objects can be used safely by any thread without additional synchronization

String

  • Strings are immutable in Java
  • If you pass strings around, this will not change the string on the memory
  • Java has special area in heap memory called String Pool
  • Before creating a new String, Java checks whether the exact value exists in the string pool
  • You can force java to create string outside of String pool by using new operator
public class Test {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hello";
        String s3 = new String("hello");
        System.out.println(s1 == s2); // true
        System.out.println(s1 == s3); // false
    }
}
  • Hence, we should not use == to compare strings

Why use StringBuilder?

  • Since String is immutable, each method you invoke on a String creates a new object and returns it.
  • Hence, each append using += causes new string objects to be created
  • StringBuilder is mutable. When you call append() it alters the internal byte[] array, rather than creating a new string object.
    • For optimization reasons, byte[] array is used to store string instead of char[] array
  • Java compiler also try to optimize wherever possible. For example it will convert expression: A + B + C to StringBuilder(A).append(B).append(C).toString() to make it efficient at the compile time itself
  • See StringBuilder and StringBuffer

Unmodifiable Collection vs Immutable

  • You can create unmodifiable collection using Collections.unmodifiableXXX() method or of() method
    • Collections.unmodifiableList(listObject)
    • List.of()
    • Set.of()
  • You can create a truly immutable collection using guava’s implementation:
    • ImmutableList.of()
  • Unmodifiable collections can be changed via reference while a truly immutable collection cannot be changed in any way
  • Example:
    • c1 is mutable (i.e. neither unmodifiable nor immutable).
    • c2 is unmodifiable: it can’t be changed itself, but if later on I change c1 then that change will be visible in c2.
Collection<String> c1 = new ArrayList<String>();
c1.add("foo");
Collection<String> c2 = Collections.unmodifiableList(c1);

How to create immutable class

  • Declare class as final so it cannot be extended.
  • Make all the fields private and final so that direct access is not allowed
  • Don’t provide setter methods for fields
  • Initialize all fields using constructor methods performing deep copy
    • If deep copy not performed then the reference can modify state
  • If there are getter methods then, perform cloning of objects to return a copy rather than returning the actual object reference.

final and immutability

class Immutable {
    private final int value;
 
    public Immutable(int value) {
        this.value = value;
    }
 
    public int getValue() { return value; }
}
 
class Mutable extends Immutable {
    private int realValue;
 
    public Mutable(int value) {
        super(value);
        realValue = value;
    }
 
    public int getValue() { return realValue; }
    public void setValue(int newValue) { realValue = newValue; }
}
 
class Printer {
    public static void printVal(Immutable immObj) {
        // assume that immObj is immutable
        System.out.println(immObj.getValue());
    }
}
 
public class Test {
    public static void main(String[] args) {
        Mutable obj = new Mutable(4);
 
        Printer.printVal(obj); // 4
 
        // assuming it is changed by another thread
        obj.setValue(8);
 
        Printer.printVal(obj); // 8
    }
}