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

В чем разница между клонированием и копированием объектов?

1.3 Junior🔥 201 комментариев
#Основы Java

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

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

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

# Разница между клонированием и копированием объектов

Краткий ответ

Копирование - присвоение ссылки одного объекта другой переменной. Обе переменные указывают на один и тот же объект в памяти.

Клонирование - создание нового объекта с теми же значениями полей. Это новый, независимый объект в памяти.

1. Копирование (Reference Copy)

Что происходит: Копируется только ссылка на объект

public class Person {
    public String name;
    public int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public static void main(String[] args) {
    Person original = new Person("Alice", 30);
    Person copy = original;  // Копирование ссылки!
    
    System.out.println(original == copy);  // true - одинаковые ссылки
    System.out.println(System.identityHashCode(original) == 
                       System.identityHashCode(copy));  // true
    
    // Изменение через одну ссылку видно через другую
    copy.name = "Bob";
    System.out.println(original.name);  // "Bob" - изменилось!
}

Визуально:

Память:
original ──┐
           ├──→ [Person объект: name="Alice", age=30]
copy ──────┘

Обе переменные указывают на ОДИН объект

Характеристики копирования:

  • Две переменные указывают на один объект
  • Изменение через одну видно через другую
  • Быстро (просто копирует адрес)
  • Экономит память

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

Что происходит: Создается новый объект, но его поля копируют ссылки исходного объекта

public class Person implements Cloneable {
    public String name;
    public Address address;  // Сложный объект
    
    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }
    
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();  // Поверхностное клонирование
    }
}

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

public static void main(String[] args) throws CloneNotSupportedException {
    Address addr = new Address("New York");
    Person original = new Person("Alice", addr);
    Person clone = (Person) original.clone();  // Поверхностное клонирование
    
    System.out.println(original == clone);  // false - разные объекты Person
    System.out.println(original.address == clone.address);  // true - одинаковые Address!
    
    // Изменение вложенного объекта видно в обоих
    clone.address.city = "Boston";
    System.out.println(original.address.city);  // "Boston" - изменилось!
}

Визуально:

Память:
original ──→ [Person: name="Alice", address ──────┐
                                                    ↓
clone ────→ [Person: name="Alice", address ──────→ [Address: city="New York"]

Желтые объекты Person разные, но они указывают на ОДИН Address

Характеристики поверхностного клонирования:

  • Создается новый объект
  • Примитивные типы копируются
  • Сложные объекты копируют ссылки
  • Проблемы с вложенными объектами

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

Что происходит: Создается новый объект, и все его вложенные объекты тоже клонируются

public class Person implements Cloneable {
    public String name;
    public Address address;
    
    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }
    
    @Override
    public Object clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone();  // Копируем сам Person
        cloned.address = (Address) address.clone();  // Клонируем вложенный Address
        return cloned;
    }
}

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

public static void main(String[] args) throws CloneNotSupportedException {
    Address addr = new Address("New York");
    Person original = new Person("Alice", addr);
    Person clone = (Person) original.clone();  // Глубокое клонирование
    
    System.out.println(original == clone);  // false
    System.out.println(original.address == clone.address);  // false - разные Address!
    
    // Изменение в клоне НЕ влияет на оригинал
    clone.address.city = "Boston";
    System.out.println(original.address.city);  // "New York" - не изменилось!
    System.out.println(clone.address.city);     // "Boston"
}

Визуально:

Память:
original ──→ [Person: name="Alice", address ──→ [Address: city="New York"]
                                                  
clone ────→ [Person: name="Alice", address ──→ [Address: city="Boston"]

ВСЕ объекты независимые

Характеристики глубокого клонирования:

  • Полная независимость объектов
  • Все вложенные объекты тоже клонируются
  • Безопасно от побочных эффектов
  • Медленнее (клонирует все)

4. Сравнение

ПараметрКопирование (Reference)Поверхностное (Shallow)Глубокое (Deep)
Новый объектНетДаДа
Ссылка на поляДаЧастично (примитивы копируются)Нет
НезависимостьЗависит от другойЧастичнаяПолная
СкоростьОчень быстроБыстроМедленнее
ПамятиМинимумСреднеМного
Побочные эффектыМногоВозможныНет
Когда использоватьЕсли нужна одна ссылкаРедко (опасно)Чаще всего

5. Практические примеры

Пример 1: Список объектов

List<Person> original = new ArrayList<>();
original.add(new Person("Alice", 30));
original.add(new Person("Bob", 25));

// Копирование ссылки (ПЛОХО)
List<Person> copy1 = original;
copy1.add(new Person("Charlie", 35));
System.out.println(original.size());  // 3 - изменилось!

// Поверхностное копирование (ЛУЧШЕ, но опасно)
List<Person> copy2 = new ArrayList<>(original);
copy2.add(new Person("David", 40));
System.out.println(original.size());  // 2 - не изменилось
// Но если изменить Person внутри copy2, изменится и в original
copy2.get(0).age = 31;
System.out.println(original.get(0).age);  // 31 - изменилось!

// Глубокое копирование (БЕЗОПАСНО)
List<Person> copy3 = original.stream()
    .map(p -> clonePerson(p))
    .collect(Collectors.toList());

Пример 2: Использование Clone

public class Student implements Cloneable {
    private String name;
    private List<String> courses;
    
    public Student(String name) {
        this.name = name;
        this.courses = new ArrayList<>();
    }
    
    public void addCourse(String course) {
        courses.add(course);
    }
    
    @Override
    public Student clone() {
        try {
            Student cloned = (Student) super.clone();
            cloned.courses = new ArrayList<>(this.courses);  // Глубокое копирование списка
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

public static void main(String[] args) {
    Student original = new Student("Alice");
    original.addCourse("Math");
    original.addCourse("Physics");
    
    Student clone = original.clone();  // Глубокое клонирование
    clone.addCourse("Chemistry");
    
    System.out.println("Original courses: " + original.courses.size());  // 2
    System.out.println("Clone courses: " + clone.courses.size());        // 3
}

6. Альтернативные подходы

Copy Constructor

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

// Использование
Person original = new Person("Alice", new Address("NYC"));
Person clone = new Person(original);  // Copy constructor

Builder pattern

Person clone = new Person.Builder(original)
    .name(original.getName())
    .address(new Address(original.getAddress()))
    .build();

JSON сериализация (для глубокого копирования)

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();
Person original = new Person("Alice", new Address("NYC"));

// Сериализуем в JSON и десериализуем
String json = mapper.writeValueAsString(original);
Person clone = mapper.readValue(json, Person.class);  // Глубокое копирование

7. Когда что использовать

Используй копирование ссылки когда:

  • Нужны две переменные указывающие на один объект
  • Хочешь синхронизировать изменения
  • Уверен в своих действиях

Используй поверхностное клонирование когда:

  • У объекта только примитивные поля
  • НЕ нужна полная независимость
  • Редко! (это обычно приводит к багам)

Используй глубокое клонирование когда:

  • Нужна полная независимость объектов
  • Есть сложные вложенные структуры
  • Правило по умолчанию

Заключение

  • Копирование - одна ссылка на объект в памяти
  • Поверхностное клонирование - новый объект, но вложенные копируют ссылки
  • Глубокое клонирование - полностью новый независимый объект
  • Используй глубокое клонирование как правило
  • Будь осторожен с копированием ссылок в многопоточных приложениях
В чем разница между клонированием и копированием объектов? | PrepBro