Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Как создать неизменяемый класс

Короткий ответ

Запись (record) — самый короткий способ. Её поля финальные, а конструктор, методы доступа, equals(), hashCode() и toString() создаются автоматически:

record Money(String currency, long cents) {}
 
void main() {
    var price = new Money("EUR", 1999);
 
    IO.println(price);
    IO.println(price.cents());
}

Результат:

Money[currency=EUR, cents=1999]
1999

Проверка в конструкторе

Компактный конструктор записи проверяет значения до присвоения полям:

record Money(String currency, long cents) {
    Money {
        if (cents < 0) {
            throw new IllegalArgumentException("cents must not be negative: " + cents);
        }
    }
}
 
void main() {
    IO.println(new Money("EUR", 500));
    new Money("EUR", -1);
}

Результат:

Money[currency=EUR, cents=500]
Exception in thread "main" java.lang.IllegalArgumentException: cents must not be negative: -1

Изменение — это новый объект

Вместо сеттеров добавьте методы, которые возвращают новый объект:

record Money(String currency, long cents) {
    Money plus(long more) {
        return new Money(currency, cents + more);
    }
}
 
void main() {
    var price = new Money("EUR", 1000);
    var total = price.plus(250);
 
    IO.println(price);
    IO.println(total);
}

Результат:

Money[currency=EUR, cents=1000]
Money[currency=EUR, cents=1250]

Коллекции внутри

Запись хранит ссылку на переданный список. Сделайте неизменяемую копию в конструкторе:

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

Результат:

[Anna]

Обычный класс

Без записи: класс final, все поля private final, нет сеттеров:

final class Point {
    private final int x;
    private final int y;
 
    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
 
    int x() { return x; }
    int y() { return y; }
}
 
void main() {
    var p = new Point(3, 4);
    IO.println(p.x() + ", " + p.y());
}

Результат:

3, 4

Частая ошибка

Хранить изменяемый список без копии. Внешний код меняет «неизменяемый» объект:

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

Результат:

[Anna, Oleg]

Функции из этого ответа

  • record — Объявляет запись — неизменяемый класс для хранения данных с готовыми equals(), hashCode() и toString().
  • final — Запрещает менять переменную после присваивания, переопределять метод или наследовать класс.
  • List.copyOf() — Создаёт неизменяемую копию коллекции в виде списка.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.