Ответ
Immutable класс — это класс, состояние которого невозможно изменить после создания объекта. Все поля финализированы, сеттеры отсутствуют, а методы не модифицируют внутреннее состояние.
Признаки immutable класса:
- Все поля объявлены как
final - Класс объявлен как
final(запрещает наследование) - Отсутствуют сеттеры (setter methods)
- Поля инициализируются только в конструкторе
- Методы не изменяют состояние объекта, а возвращают новые экземпляры
- Защита от изменения mutable объектов (если они есть в полях)
Пример реализации:
public final class ImmutablePerson {
private final String name;
private final int age;
private final List<String> hobbies;
public ImmutablePerson(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
// Защитная копия для mutable поля
this.hobbies = new ArrayList<>(hobbies);
}
public String getName() { return name; }
public int getAge() { return age; }
// Возвращаем защитную копию, а не оригинальный список
public List<String> getHobbies() {
return new ArrayList<>(hobbies);
}
// Метод для "изменения" возвращает новый объект
public ImmutablePerson withAge(int newAge) {
return new ImmutablePerson(this.name, newAge, this.hobbies);
}
}
Преимущества:
- Потокобезопасность — объекты можно безопасно использовать в многопоточных средах без синхронизации
- Простота тестирования — состояние объекта предсказуемо
- Безопасное кэширование — объекты можно кэшировать, так как они не изменятся
- Упрощённый код — отсутствуют побочные эффекты от изменения состояния
Типичные примеры в Java: String, Integer, LocalDate, BigDecimal
Ответ 18+ 🔞
А, слушай, смотри, вот эта вся хуйня про immutable классы — это вообще одна из немногих вещей в джаве, которая реально имеет смысл, а не просто для галочки в интервью.
Представь себе, блядь, кирпич. Ну, обычный кирпич. Ты его слепил, обжёг — и всё, пиздец. Он уже кирпич. Ты его не можешь взять и пальцем в нём дырку просверлить, чтобы он стал пористее. Хочешь другой кирпич — лепи и жги новый. Вот immutable объект — это такой программный кирпич, ёпта. Создали — и он застыл, как мумия. Ни тебе сеттеров, ни изменения полей, ни хуя.
Как его, этого мутанта, опознать, блядь?
- Все поля —
final. То есть после того, как их в конструкторе запихнули, они там и остаются до скончания времён. Попробуй изменить — компилятор тебе ебальник набьёт. - Сам класс —
final. Чтобы какой-нибудь хитрожопый наследник не пришёл и не наделал делов, переопределив методы так, что они начнут менять состояние. Нахуй такие сюрпризы. - Сеттеров — ноль. Вообще. Ни одного. Если видишь
setName()— это уже не immutable, а просто какая-то шлюха mutable. - Всё инициализируется в конструкторе. Один раз и навсегда. Как в ЗАГСе, только без разводов.
- Методы — святые. Они не портят объект. Если нужно что-то «поменять» — они тебе новый объект отштампуют и отдадут. Старый останется лежать, как в мавзолее.
- Самое важное, блядь! Если внутри класса есть какое-то изменяемое поле, например,
ListилиMap— ты должен его защитить, как зеницу ока. В конструкторе делаешь копию, в геттере тоже возвращаешь копию. Иначе какой-то ушлый пидорас снаружи получит ссылку на твой внутренний список и начнёт там хуйню творить, а твой «неизменяемый» объект будет тихо срать кирпичи.
Вот, смотри, как это выглядит в коде, сука:
public final class ImmutablePerson {
private final String name;
private final int age;
private final List<String> hobbies;
public ImmutablePerson(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
// Вот тут, блядь, ключевой момент! Не присваиваем список как есть!
// Делаем защитную копию, чтобы если нам передали список, а потом его изменили — у нас внутри всё осталось как было.
this.hobbies = new ArrayList<>(hobbies);
}
public String getName() { return name; }
public int getAge() { return age; }
// И тут тоже! Не возвращаем ссылку на свой внутренний список, а новую копию.
// Иначе получивший её чувак начнёт в нём ковыряться.
public List<String> getHobbies() {
return new ArrayList<>(hobbies);
}
// Хочешь «изменить» возраст? Блядь, никаких изменений!
// Вот тебе новый объект со старым именем, новым возрастом и старыми же увлечениями.
public ImmutablePerson withAge(int newAge) {
return new ImmutablePerson(this.name, newAge, this.hobbies);
}
}
А нахуя это всё, спрашивается?
- Потокобезопасность, ебать её в сраку! Объект, который не меняется, можно таскать по всем потокам, хоть тысяче. Никаких блокировок, синхронизаций,
volatile— нихуя не нужно. Он же мёртвый уже. Его только читать. - Тестировать — одно удовольствие. Состояние известно раз и навсегда. Не будет такого, что ты передал объект в метод, а он тебе его там испоганил, и ты потом три дня ищешь, где же жопа.
- Кэшировать можно на раз. Создал один раз — и клади хоть в статик, хоть в кэш приложения. Он не протухнет, не изменится. Как консервы.
- Код проще. Нет этих вечных танцев с бубном: «а что если его изменили тут, а что если там». Создали — и забыли. Живёт себе.
В самой джаве этого добра — овердохуища: String, все обёртки типа Integer, LocalDate, BigDecimal. Они все такие, немые и неизменные. Попробуй изменить строку — нихуя, только новую получишь. Вот и вся философия.