Нарушает ли наследование иммутабельность класса в Java?

Ответ

Да, наследование может нарушить иммутабельность класса, если не предприняты специальные меры.

Иммутабельный (неизменяемый) класс должен гарантировать, что его состояние не может быть изменено после создания. Наследование создает риски:

  1. Подкласс может добавить изменяемые поля.
  2. Подкласс может переопределить методы, которые в родительском классе казались "безопасными", и изменить внутреннее состояние.

Пример нарушения иммутабельности через наследование:

// Предположительно иммутабельный родительский класс
public class ImmutablePoint {
    private final int x;
    private final int y;

    public ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public int getX() { return x; }
    public int getY() { return y; }
}

// Подкласс, который делает объект изменяемым
public class MutablePoint extends ImmutablePoint {
    private int z; // Добавлено изменяемое поле!

    public MutablePoint(int x, int y) {
        super(x, y);
    }
    public void setZ(int z) { this.z = z; } // Метод-мутатор
}
// Теперь объект, воспринимаемый как ImmutablePoint, может изменяться.

Способы защиты иммутабельности:

  1. Объявите класс как final. Это предотвращает создание подклассов.
    public final class ImmutablePoint { ... }
  2. Сделайте все конструкторы private или package-private и используйте фабричные методы. Это контролирует создание экземпляров.
  3. Отдавайте предпочтение композиции перед наследованием. Включайте функциональность, делегируя вызовы внутреннему объекту, а не расширяя класс.

Вывод: Без защиты (например, ключевого слова final) иммутабельность класса не гарантирована в контексте наследования.

Ответ 18+ 🔞

Ну вот, сидишь ты, такой весь в белом халате, пишешь свой идеальный иммутабельный класс. Всё final, поля приватные, геттеры — огурцом. Чувствуешь себя богом ООП, сука. «Никто не посмеет изменить мои священные данные!» — думаешь ты.

А потом приходит какой-нибудь хитрожопый разработчик и такой: «О, классный ImmutablePoint! Ща я от него наследусь, да сделаю MutablePoint, да добавлю туда поле z с сеттером!».

И всё, пиздец твоей иммутабельности, ёпта. Объект, который ты по всему коду передавал как священную неизменяемую сущность, вдруг начинает дергаться и менять жопу, потому что на самом деле это был его сын-выродок.

// Твой красавчик
public class ImmutablePoint {
    private final int x;
    private final int y;
    // ... геттеры, всё чинно
}

// А это его сынок-распиздяй
public class MutablePoint extends ImmutablePoint {
    private int z; // Смотри-ка, изменяемое поле, блядь!
    public void setZ(int z) { this.z = z; } // И сеттер, нате вам!
}

И что получается? Получается пиздец. Ты думал, что передаешь каменную скрижаль, а передал пластилин, в который уже кто-то тычет пальцем.

Как защититься от таких нападок, блядь?

  1. Самый простой и действенный способ — объяви класс final. Это как сказать: «Нахуй, ребята, от меня никто не унаследует, я самодостаточная единица, в рот меня чих-пых!».

    public final class ImmutablePoint { ... } // Всё, приехали. Точка.
  2. Спрятать конструкторы. Сделать их private и выплюнуть наружу только статический фабричный метод. Пусть объекты рождаются только в одном месте, под присмотром. Хоть это и не остановит упоротого наследника на 100%, но усложнит ему жизнь.

  3. Вообще забыть про наследование, где это возможно. Композиция, ёба! Не «является», а «имеет». Вместо того чтобы наследоваться, включи нужную функциональность внутрь себя как отдельный объект и делегируй ему вызовы. Меньше головной боли, честно.

Вывод, блядь: Если твой класс должен быть по-настоящему иммутабельным, а не просто притворяться, — ставь final и спи спокойно. Иначе какой-нибудь умник своим наследованием разворотит всю твою красивую архитектуру, как таракан под плинтусом. Доверия к таким открытым классам — ноль ебать.