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

Как правильно клонировать объекты

2.7 Senior🔥 131 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Клонирование объектов в Java

Клонирование объектов — это критичный навык, который часто приводит к ошибкам, если делать его неправильно. Существует несколько подходов, каждый со своими плюсами и минусами.

Поверхностное копирование (Shallow Copy)

Поверхностное копирование создаёт новый объект, но ссылки на вложенные объекты остаются теми же.

// Неправильный способ - просто присваивание
Person person1 = new Person("John", new Address("New York"));
Person person2 = person1; // Это ещё не копирование!

person2.getAddress().setCity("Boston");
System.out.println(person1.getAddress().getCity()); // Boston! Обе переменные указывают на один объект

Реализация через clone():

public class Person implements Cloneable {
    private String name;
    private Address address;
    
    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone(); // Поверхностное копирование
    }
}

// Использование
Person person1 = new Person("John", new Address("New York"));
Person person2 = person1.clone();

person2.setName("Jane");
System.out.println(person1.getName()); // John - независимо

person2.getAddress().setCity("Boston");
System.out.println(person1.getAddress().getCity()); // Boston! Адреса связаны

Глубокое копирование (Deep Copy)

Глубокое копирование рекурсивно копирует все вложенные объекты.

public class Person implements Cloneable {
    private String name;
    private Address address;
    
    @Override
    public Person clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone();
        // Глубокое копирование вложенного объекта
        cloned.address = this.address.clone();
        return cloned;
    }
}

public class Address implements Cloneable {
    private String city;
    
    @Override
    public Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }
}

// Использование
Person person1 = new Person("John", new Address("New York"));
Person person2 = person1.clone();

person2.getAddress().setCity("Boston");
System.out.println(person1.getAddress().getCity()); // New York - полностью независимо!

Copy Constructor (Рекомендуемый подход)

Copy constructor — это явный и читаемый способ клонирования:

public class Person {
    private String name;
    private Address address;
    
    // Copy constructor
    public Person(Person other) {
        this.name = other.name;
        this.address = new Address(other.address); // Глубокое копирование
    }
}

public class Address {
    private String city;
    
    public Address(Address other) {
        this.city = other.city;
    }
}

// Использование - выглядит ясно
Person person1 = new Person("John", new Address("New York"));
Person person2 = new Person(person1); // Явное клонирование

Использование Builder Pattern

Для сложных объектов часто используют Builder:

public class PersonBuilder {
    private String name;
    private Address address;
    
    public PersonBuilder(Person original) {
        this.name = original.getName();
        this.address = new Address(original.getAddress());
    }
    
    public PersonBuilder withName(String name) {
        this.name = name;
        return this;
    }
    
    public Person build() {
        return new Person(name, address);
    }
}

// Использование
Person person2 = new PersonBuilder(person1)
    .withName("Jane")
    .build();

Использование Jackson для сериализации

Для сложных структур часто используют JSON как промежуточный формат:

ObjectMapper objectMapper = new ObjectMapper();
Person person1 = new Person("John", new Address("New York"));

// Глубокое копирование через JSON
String json = objectMapper.writeValueAsString(person1);
Person person2 = objectMapper.readValue(json, Person.class);

Использование Apache Commons Lang

SerializationUtils для глубокого копирования сериализуемых объектов:

public class Person implements Serializable {
    private String name;
    private Address address;
}

public class Address implements Serializable {
    private String city;
}

// Использование
Person person1 = new Person("John", new Address("New York"));
Person person2 = SerializationUtils.clone(person1);

Сравнение подходов

ПодходПлюсыМинусы
clone()Встроенный механизмChecked exception, сложно с вложенными объектами
Copy ConstructorЯвно и читаемоНужно писать для каждого класса
BuilderГибкостьБольше кода
JacksonУниверсальноOverhead сериализации, требует Serializable
Commons LangПростоЗависимость, требует Serializable

Лучшие практики

  1. Отдавай предпочтение Copy Constructor — это самый явный и safe способ
  2. Для immutable объектов копирование не требуется
  3. Избегай clone() interface в новом коде — это считается antipattern
  4. Используй вспомогательные библиотеки для сложных случаев
  5. Помни о Collections — List.copy(), Map.copyOf() (Java 10+) создают неизменяемые копии
// Правильно копировать коллекции
List<String> original = new ArrayList<>(List.of("a", "b", "c"));
List<String> copy = new ArrayList<>(original); // Неглубокое копирование списка
List<String> immutableCopy = List.copyOf(original); // Неизменяемая копия (Java 10+)

Выбор метода зависит от сложности объекта, требований к производительности и читаемости кода.