====== 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.