← Назад к вопросам
Приведи пример иммутабельного объекта
1.3 Junior🔥 181 комментариев
#ORM и Hibernate#Spring Boot и Spring Data#Базы данных и SQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Ответ: Приведи пример иммутабельного объекта
Краткий ответ и примеры
Иммутабельный объект — это объект, который не может быть изменён после создания. В Java есть отличные примеры: String, Integer, LocalDate. Я покажу как создавать свои иммутабельные классы и почему это важно.
Пример 1: Иммутабельный класс User
// ✅ ПРАВИЛЬНО - полностью иммутабельный
public final class ImmutableUser {
// final поля — не могут быть переосвязаны
private final Long id;
private final String username;
private final String email;
private final LocalDateTime createdAt;
// Конструктор принимает все параметры
public ImmutableUser(Long id, String username,
String email, LocalDateTime createdAt) {
this.id = Objects.requireNonNull(id);
this.username = Objects.requireNonNull(username);
this.email = Objects.requireNonNull(email);
this.createdAt = Objects.requireNonNull(createdAt);
}
// Только getters, БЕЗ setters
public Long getId() {
return id;
}
public String getUsername() {
return username;
}
public String getEmail() {
return email;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
// Если нужно изменить данные — создаём НОВЫЙ объект
public ImmutableUser withEmail(String newEmail) {
// Builder pattern или copy constructor
return new ImmutableUser(this.id, this.username,
newEmail, this.createdAt);
}
// equals и hashCode обязательны
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ImmutableUser that = (ImmutableUser) o;
return Objects.equals(id, that.id) &&
Objects.equals(username, that.username) &&
Objects.equals(email, that.email) &&
Objects.equals(createdAt, that.createdAt);
}
@Override
public int hashCode() {
return Objects.hash(id, username, email, createdAt);
}
@Override
public String toString() {
return "ImmutableUser{" +
"id=" + id +
", username='" + username + \'\' +
", email='" + email + \'\' +
", createdAt=" + createdAt +
"}";
}
}
// Использование
public class ImmutableUserExample {
public static void main(String[] args) {
ImmutableUser user = new ImmutableUser(
1L, "john", "john@example.com", LocalDateTime.now()
);
System.out.println(user); // ImmutableUser{...}
// user.setEmail("newemail@example.com"); // ❌ Нет такого метода!
// Для изменения — создаём НОВЫЙ объект
ImmutableUser updatedUser = user.withEmail("newemail@example.com");
System.out.println(updatedUser); // Новый объект
System.out.println(user); // Оригинальный не изменился
}
}
Пример 2: Иммутабельный класс Point (геометрия)
public final class Point {
private final double x;
private final double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
// Операции возвращают НОВЫЙ объект
public Point translate(double dx, double dy) {
return new Point(this.x + dx, this.y + dy);
}
public Point scale(double factor) {
return new Point(this.x * factor, this.y * factor);
}
public double distance(Point other) {
double dx = other.x - this.x;
double dy = other.y - this.y;
return Math.sqrt(dx * dx + dy * dy);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return Double.compare(point.x, x) == 0 &&
Double.compare(point.y, y) == 0;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return String.format("Point(%.2f, %.2f)", x, y);
}
}
// Использование
public class PointExample {
public static void main(String[] args) {
Point p1 = new Point(0, 0);
System.out.println(p1); // Point(0.00, 0.00)
Point p2 = p1.translate(3, 4);
System.out.println(p2); // Point(3.00, 4.00)
System.out.println(p1); // Point(0.00, 0.00) — не изменился!
Point p3 = p2.scale(2);
System.out.println(p3); // Point(6.00, 8.00)
System.out.println(p1.distance(p2)); // 5.0
}
}
Пример 3: Java встроенные иммутабельные классы
public class BuiltInImmutableExamples {
public static void main(String[] args) {
// 1. String
String s1 = "Hello";
String s2 = s1.toUpperCase();
System.out.println(s1); // "Hello" — не изменилась
System.out.println(s2); // "HELLO" — новая строка
// 2. Integer
Integer i1 = 42;
Integer i2 = i1; // Безопасно — не может измениться
System.out.println(i1 == i2); // true (одинаковый кэшированный объект)
// 3. LocalDate
LocalDate date1 = LocalDate.of(2024, 1, 15);
LocalDate date2 = date1.plusDays(10);
System.out.println(date1); // 2024-01-15 — не изменилась
System.out.println(date2); // 2024-01-25 — новая дата
// 4. Collections.unmodifiableList
List<String> original = new ArrayList<>(Arrays.asList("a", "b", "c"));
List<String> unmodifiable = Collections.unmodifiableList(original);
System.out.println(unmodifiable); // [a, b, c]
// unmodifiable.add("d"); // ❌ UnsupportedOperationException
// 5. Map.of() (Java 9+) — иммутабельная карта
Map<String, Integer> map = Map.of(
"Alice", 100,
"Bob", 95,
"Charlie", 88
);
// map.put("David", 92); // ❌ UnsupportedOperationException
}
}
Пример 4: Иммутабельный класс с Builder Pattern
public final class ImmutableProduct {
private final String id;
private final String name;
private final double price;
private final String description;
private final List<String> tags; // Коллекция
// Приватный конструктор
private ImmutableProduct(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.price = builder.price;
this.description = builder.description;
// Копируем коллекцию, чтобы она была иммутабельной
this.tags = Collections.unmodifiableList(
new ArrayList<>(builder.tags)
);
}
// Статический builder класс
public static class Builder {
private String id;
private String name;
private double price;
private String description = "";
private List<String> tags = new ArrayList<>();
public Builder id(String id) {
this.id = Objects.requireNonNull(id);
return this;
}
public Builder name(String name) {
this.name = Objects.requireNonNull(name);
return this;
}
public Builder price(double price) {
if (price < 0) throw new IllegalArgumentException("Price must be >= 0");
this.price = price;
return this;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Builder addTag(String tag) {
this.tags.add(tag);
return this;
}
public ImmutableProduct build() {
Objects.requireNonNull(id);
Objects.requireNonNull(name);
return new ImmutableProduct(this);
}
}
public String getId() { return id; }
public String getName() { return name; }
public double getPrice() { return price; }
public String getDescription() { return description; }
public List<String> getTags() { return tags; } // Возвращаем иммутабельный список
@Override
public String toString() {
return "ImmutableProduct{" +
"id='" + id + \'\' +
", name='" + name + \'\' +
", price=" + price +
", description='" + description + \'\' +
", tags=" + tags +
"}";
}
}
// Использование
public class BuilderExample {
public static void main(String[] args) {
ImmutableProduct product = new ImmutableProduct.Builder()
.id("P001")
.name("Laptop")
.price(999.99)
.description("High-performance laptop")
.addTag("electronics")
.addTag("computers")
.build();
System.out.println(product);
// ImmutableProduct{id='P001', name='Laptop', price=999.99, ...}
// Коллекция тоже иммутабельна
// product.getTags().add("books"); // ❌ UnsupportedOperationException
}
}
Почему иммутабельные объекты важны?
public class ImmutableityBenefits {
// 1. Thread-safety без синхронизации
private ImmutableUser user = new ImmutableUser(...);
public void multiThreadAccess() {
// Несколько потоков могут безопасно читать user
// Никто не сможет его изменить
Thread t1 = new Thread(() -> System.out.println(user.getUsername()));
Thread t2 = new Thread(() -> System.out.println(user.getEmail()));
t1.start();
t2.start();
// ✅ Безопасно, нет Race conditions
}
// 2. Безопасное использование в HashMap
public void mapUsage() {
Map<ImmutableUser, String> cache = new HashMap<>();
ImmutableUser user = new ImmutableUser(...);
cache.put(user, "cached_value");
// ✅ Безопасно, hashCode никогда не изменится
System.out.println(cache.get(user)); // "cached_value"
}
// 3. Предсказуемость
public Point calculateCenter(Point p1, Point p2) {
// p1 и p2 гарантированно не изменяются внутри функции
return new Point(
(p1.getX() + p2.getX()) / 2,
(p1.getY() + p2.getY()) / 2
);
}
// 4. Кэширование
private final Map<ImmutableUser, Integer> ageCache = new HashMap<>();
public int getAge(ImmutableUser user) {
if (ageCache.containsKey(user)) {
return ageCache.get(user);
}
int age = calculateAge(user);
ageCache.put(user, age);
return age;
}
}
Что НЕ делать (примеры ошибок)
// ❌ НЕПРАВИЛЬНО - объект мутабельный
public class MutableUser {
public String username; // public, может быть изменён
public String email; // public
// Есть setter — может быть изменён
public void setEmail(String email) {
this.email = email;
}
}
// ❌ НЕПРАВИЛЬНО - в классе есть mutable коллекция
public final class BadImmutableUser {
private final List<String> tags; // final, но список мутабельный!
public BadImmutableUser(List<String> tags) {
this.tags = tags; // ❌ Кто-то может изменить внешний список
}
public List<String> getTags() {
return tags; // ❌ Кто-то может добавить/удалить элементы
}
}
// ✅ ПРАВИЛЬНО - защита от мутирования коллекции
public final class GoodImmutableUser {
private final List<String> tags;
public GoodImmutableUser(List<String> tags) {
// Копируем список, чтобы внешний не влиял
this.tags = Collections.unmodifiableList(
new ArrayList<>(tags)
);
}
public List<String> getTags() {
// Возвращаем иммутабельный список
return tags;
}
}
Таблица: Иммутабельность в Java
| Класс | Иммутабельный | Примечание |
|---|---|---|
| String | ✅ Да | Золотой стандарт |
| Integer | ✅ Да | Boxing type |
| LocalDate | ✅ Да | Java 8 Date/Time |
| ArrayList | ❌ Нет | Mutable collection |
| HashMap | ❌ Нет | Mutable collection |
| Collections.unmodifiableList() | ✅ Да | Wrapper для защиты |
| Arrays.asList() | ⚠️ Частично | Fixed-size но не fully immutable |
| Map.of() | ✅ Да | Java 9+ immutable map |
| List.of() | ✅ Да | Java 9+ immutable list |
Когда использовать иммутабельные объекты
1. Data Transfer Objects (DTO)
→ Они создаются один раз и не меняются
2. Entities в многопоточных системах
→ Безопасный доступ без синхронизации
3. Ключи для HashMap/HashSet
→ hashCode() не должен меняться
4. Параметры для асинхронных операций
→ Значения не подвержены гонке данных
5. Кэширование
→ Иммутабельные объекты безопасно кэшировать
Итог
- Иммутабельный объект — не может быть изменён после создания
- Ключевые признаки:
- Класс
final(нельзя наследовать) - Все поля
final(не переосвязываются) - Нет setters (нельзя изменить)
- Внутренние коллекции копируются и оборачиваются
- Класс
- Преимущества: thread-safety, предсказуемость, кэширование
- Примеры в Java: String, Integer, LocalDate, List.of(), Map.of()
- Использование: DTOs, entities, ключи карт, асинхронные операции