← Назад к вопросам

Приведи пример иммутабельного объекта

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. Кэширование
   → Иммутабельные объекты безопасно кэшировать

Итог

  1. Иммутабельный объект — не может быть изменён после создания
  2. Ключевые признаки:
    • Класс final (нельзя наследовать)
    • Все поля final (не переосвязываются)
    • Нет setters (нельзя изменить)
    • Внутренние коллекции копируются и оборачиваются
  3. Преимущества: thread-safety, предсказуемость, кэширование
  4. Примеры в Java: String, Integer, LocalDate, List.of(), Map.of()
  5. Использование: DTOs, entities, ключи карт, асинхронные операции