Является ли класс неизменяемым, если он объявлен как final, имеет private поля и не имеет сеттеров?

«Является ли класс неизменяемым, если он объявлен как final, имеет private поля и не имеет сеттеров?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Не обязательно. Этого набора условий недостаточно для гарантии полной неизменяемости (immutability). Критически важный недостающий элемент — защита изменяемых (mutable) полей.

Полный список требований к неизменяемому классу:

  1. Класс объявлен как final.
  2. Все поля объявлены как private final.
  3. Отсутствуют сеттеры (setter methods).
  4. Все поля инициализируются в конструкторе.
  5. Защита mutable-полей: Любые поля, являющиеся ссылками на изменяемые объекты (массивы, коллекции, другие mutable-классы), должны быть защищены:
    • При сохранении: Не хранить переданную извне ссылку. Вместо этого выполнять защитное копирование (defensive copy) в новый объект.
    • При возврате: Не возвращать прямую ссылку на внутреннее поле. Возвращать либо копию, либо неизменяемую обертку (например, Collections.unmodifiableList()).

Пример уязвимого "псевдо-неизменяемого" класса:

public final class VulnerablePerson {
    private final String name;
    private final List<String> hobbies; // Mutable поле!

    // ОШИБКА: сохраняем прямую ссылку на внешний список
    public VulnerablePerson(String name, List<String> hobbies) {
        this.name = name;
        this.hobbies = hobbies; // Опасность!
    }

    // ОШИБКА: возвращаем прямую ссылку на внутренний список
    public List<String> getHobbies() {
        return hobbies; // Опасность!
    }
}
// Вызывающий код может изменить список после создания объекта.