Гарантирует ли ключевое слово final в Java, что класс является полностью иммутабельным?

Ответ

Нет. Объявление класса как final лишь запрещает его наследование, но не гарантирует иммутабельности (неизменяемости состояния его объектов).

Критерии иммутабельного класса:

  1. final класс (чтобы нельзя было создать изменяемый подкласс).
  2. Все поля объявлены как private final (инициализируются только в конструкторе).
  3. Отсутствие сеттеров (setter методов), изменяющих состояние.
  4. Защищённое копирование (defensive copying) для mutable-полей: если класс содержит ссылки на изменяемые объекты (например, массивы, коллекции), их необходимо копировать при передаче в конструктор и при возврате через геттер.

Пример иммутабельного класса:

public final class ImmutablePoint {
    private final int x;
    private final int y;

    public ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
    // Только геттеры, возвращающие примитивы (или копии mutable-объектов)
    public int getX() { return x; }
    public int getY() { return y; }
}

Класс, объявленный как final, но имеющий публичные сеттеры или изменяемые поля, не будет иммутабельным.

Ответ 18+ 🔞

А, ну это же классика, блядь! Сейчас объясню, как есть, без этих ваших академических закидонов.

Смотри, объявить класс final — это как поставить на дверь замок и сказать: «Наследоваться тут, блядь, никому нельзя!». Но это же не значит, что внутри квартиры бардака нет, сука! Можно ж и в своей собственной, ёпта, берлоге всё по углам разбросать, хоть замок хоть какой.

Чтобы объект был по-настоящему иммутабельным, то есть святым, неприкасаемым, чтоб его состояние с момента рождения и до самой смерти не менялось, нужно соблюсти целый ритуал, блядь:

  1. Класс — final. Ну это мы уже поняли, чтоб никакой хитрожопый наследничек не пришёл и не наворотил дел в методах.
  2. Все поля — private final. Это святое! private — чтоб с улицы не тыкали, final — чтоб один раз в конструкторе присвоили и всё, пиши пропало, больше ни шагу.
  3. Никаких сеттеров. Вообще, нахуй! Какой «set», о чём вы? Родился объект с данными — с ними и помирай. Никаких «ой, давай-ка я тут x поменяю».
  4. Защитное копирование, ёбана! Вот тут многие обоссываются. Если твой иммутабельный чувак хранит у себя в полях ссылку на какой-нибудь массив или ArrayList (а они, сука, изменяемые!), то тут надо быть как шпион. Когда тебе этот массив передают в конструктор — ты делаешь его копию и свою копию кладёшь в поле. Когда просят через геттер массив вернуть — ты опять возвращаешь копию, а не оригинал! А то придут извне, твой же массив перепишут, и твой «иммутабельный» объект охуеет посреди процесса.

Вот смотри, пример правильного, честного иммутабельного класса:

public final class ImmutablePoint {
    private final int x;
    private final int y;

    public ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
    // Только геттеры, возвращающие примитивы (или копии mutable-объектов)
    public int getX() { return x; }
    public int getY() { return y; }
}

Видишь? Всё просто, как три копейки. Примитивы int — они и так копируются, с ними заморачиваться не надо.

А теперь представь класс, который объявлен как final, но у него поля не final и есть публичный сеттер. Это ж какой-то полупидор получается, блядь! Формально — final класс, а по факту — состояние меняется как хочешь. Иммутабельным его назвать — язык сломаешь.

Так что запомни: final на классе — это необходимое, но овердохуища недостаточное условие. Нужен весь комплекс мер, как в аптеке, блядь.