====== Immutable objects ====== An immutable object cannot be modified after it was created. Instead, a copy of the object is returned. ===== Advantages ===== * **Thread safety**: they cannot change its state, so they cannot be corrupted by thread interference or observed in an inconsistent state. * **Atomicity of failure**: if an immutable object throws an exception, it's never left in an undesirable or indeterminate state. * **Predictability**: objects won't change due to coding mistakes or by 3rd party libraries. As long as we reference a data structure, we know it is the same as at the time of its creation. * **Validity**: is not needed to be tested again and again. Once we create the immutable object and test its validity once, we know that it will be valid indefinitely. ===== Disadvantages ===== Immutable classes require a separate object for each distinct value. Creating these objects can be costly, especially if they are large. ===== How to implement an immutable object ===== * make fields private and final * methods that alters the object state should return new objects * make copies of caller provided data (except primitives or immutable objects) * ensure the class cannot be overridden (make the class final, or use static factories and keep constructors private) * don't provide setter methods for variables. ===== Example of immutable class ===== import java.util.ArrayList; import java.util.List; final class Record { private final long id; private final String name; private final List tokens; public Record(long id, String name, List tokens) { this.id = id; this.name = name; /* This works, but: - it will always duplicate elements - the same copy should be used also in getTokens() method. */ //this.tokens = new ArrayList<>(tokens); /* If tokens is already unmodifiable (for ex. was formed using List.of), then it will not duplicate elements. Also, it will prevent the tokens to be modified (it will throw java.lang.UnsupportedOperationException). */ this.tokens = List.copyOf(tokens); } public long getId() { return id; } public String getName() { return name; } /** * No simple setters, return another copy of the object. * @param name * @return Record */ public Record withName(String name) { return new Record(id, name, tokens); } public List getTokens() { //return new ArrayList<>(tokens); return tokens; } @Override public String toString() { return "Record (id = " + id + ", name='" + name + '\'' + ", tokens=" + tokens + ")"; } } class TestRecord { public static void main(String[] args) { List tokens = new ArrayList<>(); tokens.add("Token 1"); tokens.add("Token 2"); Record record = new Record(1, "One", tokens); System.out.println(record); tokens.remove(0); System.out.println(record); System.out.println(record.withName("Two")); record.getTokens().add("Token 3"); System.out.println(record); } } Output: Record (id = 1, name='One', tokens=[Token 1, Token 2]) Record (id = 1, name='One', tokens=[Token 1, Token 2]) Record (id = 1, name='Two', tokens=[Token 1, Token 2]) Exception in thread "main" java.lang.UnsupportedOperationException at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:71) at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:75) at ro.medjava.immutability.TestRecord.main(Record.java:65) ===== Immutable classes in JDK ===== Some immutable classes from java api: * java.lang.String * java.lang.Integer * java.io.File * java.util.Locale * java.awt.Font * java.net.InetSocketAddress Immutable classes can also be created using builder pattern. Builder Pattern is a better option if the immutable class has a lot of attributes and some of them are optional.