Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Immutability

An immutable object can't be changed after it's created: all its fields stay as the constructor set them. To "change" such an object, you create a new one. Immutable objects are easier to work with: you can safely pass them to any method or thread — nobody will spoil the data behind your back.

Immutable classes in Java

You've already used immutable objects. String, Integer and LocalDate don't change — their methods return new objects:

void main() {
    String name = "anna";
    String upper = name.toUpperCase();
 
    IO.println(name);
    IO.println(upper);
}

Output:

anna
ANNA

toUpperCase() didn't change the string name; it returned a new one.

Your own immutable class

To make a class immutable, declare all its fields final (they can be assigned only in the constructor), write no setters, and have "modifying" methods return a new object:

final class Money {
    private final int cents;
 
    Money(int cents) {
        this.cents = cents;
    }
 
    Money plus(Money other) {
        return new Money(cents + other.cents);
    }
 
    @Override
    public String toString() {
        return "$" + cents / 100 + "." + String.format("%02d", cents % 100);
    }
}
 
void main() {
    Money price = new Money(1250);
    Money total = price.plus(new Money(399));
 
    IO.println(price);
    IO.println(total);
}

Output:

$12.50
$16.49

The final on the class itself forbids inheritance: otherwise a subclass could add mutable fields.

Records

Records are immutable by default: their fields are automatically final, and there are no setters. It's the shortest way to create an immutable class. A "change" is done by creating a new record:

record Point(int x, int y) {
    Point withX(int newX) {
        return new Point(newX, y);
    }
}
 
void main() {
    Point start = new Point(1, 2);
    Point moved = start.withX(10);
 
    IO.println(start);
    IO.println(moved);
}

Output:

Point[x=1, y=2]
Point[x=10, y=2]

Immutable collections

List.of, Set.of and Map.of create immutable collections. Any attempt to change them throws an exception. List.copyOf makes an immutable copy of an existing list:

void main() {
    List<String> source = new ArrayList<>(List.of("tea", "coffee"));
    List<String> copy = List.copyOf(source);
 
    source.add("cocoa");
 
    IO.println(source);
    IO.println(copy);
}

Output:

[tea, coffee, cocoa]
[tea, coffee]

The copy didn't change along with the source list. There's also Collections.unmodifiableList(source) — but that's not a copy, it's a view: you can't change anything through it, yet changes to source show up in it.

A defensive copy

If an object receives a mutable list, a final field doesn't help: the list can still be changed from outside. That's why the constructor makes a defensive copy:

record Team(String name, List<String> members) {
    Team {
        members = List.copyOf(members);
    }
}
 
void main() {
    List<String> people = new ArrayList<>(List.of("Anna", "Oleg"));
    Team team = new Team("Backend", people);
 
    people.add("Ira");
 
    IO.println(team.members());
}

Output:

[Anna, Oleg]

The record's compact constructor replaced the given list with an immutable copy. Adding Ira to the original list didn't affect the team.

Common mistake

final on a variable forbids assigning a different object to it, but doesn't stop the object itself from changing. A list declared final can be added to as much as you like, whereas a List.of list really can't be changed:

void main() {
    final List<String> tasks = new ArrayList<>();
    tasks.add("Buy bread");
    IO.println(tasks);
 
    List<String> fixed = List.of("Buy bread");
    fixed.add("Call Oleg");
}

Output:

[Buy bread]
Exception in thread "main" java.lang.UnsupportedOperationException
	at Main.main(Main.java:7)

The UnsupportedOperationException from List.of has no message — only the exception type is shown. Immutability is a property of the object, not of the variable: final protects only the reference.

Thanks for reading!

Donate to the site — we work hard for you.