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

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

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

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

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

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

Поверхностное vs Глубокое клонирование

Клонирование объектов — это частая операция в Java, но многие разработчики не понимают разницу между поверхностным (shallow) и глубоким (deep) копированием. Это различие критично для избежания трудноловимых багов.

Основы: Как работает присваивание объектов

public class User {
    private String name;
    private Address address;  // Вложенный объект
    
    public User(String name, Address address) {
        this.name = name;
        this.address = address;
    }
}

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

// Просто присваивание — ссылка на один и тот же объект
public class Main {
    public static void main(String[] args) {
        User user1 = new User("John", new Address("NYC", "10001"));
        User user2 = user1;  // НЕ копирование, просто ссылка на тот же объект
        
        // Это один и тот же объект!
        System.out.println(user1 == user2);  // true
    }
}

Для истинного копирования нужны методы clone() или copy конструкторы.

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

Shallow copy копирует ТОЛЬКО сам объект и примитивные поля, но не копирует ссылки на другие объекты. Вложенные объекты остаются одной и той же ссылкой!

Реализация через Cloneable

public class Address implements Cloneable {
    private String city;
    private String zipCode;
    
    public Address(String city, String zipCode) {
        this.city = city;
        this.zipCode = zipCode;
    }
    
    public void setCity(String city) {
        this.city = city;
    }
    
    @Override
    public Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();  // Поверхностное копирование
    }
}

public class User implements Cloneable {
    private String name;
    private Address address;
    
    public User(String name, Address address) {
        this.name = name;
        this.address = address;
    }
    
    @Override
    public User clone() throws CloneNotSupportedException {
        return (User) super.clone();  // Shallow copy
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public Address getAddress() {
        return address;
    }
}

// Проблема shallow copy
public class ShallowCopyProblem {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("NYC", "10001");
        User user1 = new User("John", address);
        User user2 = user1.clone();  // Shallow copy
        
        // Это разные User объекты
        System.out.println(user1 == user2);  // false
        
        // НО address — один и тот же объект!
        System.out.println(user1.getAddress() == user2.getAddress());  // true (ПРОБЛЕМА!)
        
        // Изменение address в одном влияет на другой
        user2.getAddress().setCity("Los Angeles");
        
        // Неожиданный побочный эффект!
        System.out.println(user1.getAddress().getCity());  // "Los Angeles" (было "NYC")
    }
}

Визуально:

После user2 = user1.clone() (shallow copy)

user1 ──→ ┌──────────────┐
          │ User object  │
          ├──────────────┤
          │ name: John   │
user2 ──→ │ address  ───┐│  ← ОДН И ТА ЖЕ ссылка
          │            │├─┐
          └──────────────┘ │
                           │
                        ┌──▼─────────────────┐
                        │ Address object     │
                        ├────────────────────┤
                        │ city: "NYC"        │
                        │ zipCode: "10001"   │
                        └────────────────────┘

Изменение address влияет на обе User копии!

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

Deep copy копирует ВСЁ — сам объект, примитивные поля И все вложенные объекты рекурсивно.

Реализация через рекурсивный clone()

public class Address implements Cloneable {
    private String city;
    private String zipCode;
    
    public Address(String city, String zipCode) {
        this.city = city;
        this.zipCode = zipCode;
    }
    
    public void setCity(String city) {
        this.city = city;
    }
    
    public String getCity() {
        return city;
    }
    
    @Override
    public Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }
}

public class User implements Cloneable {
    private String name;
    private Address address;
    
    public User(String name, Address address) {
        this.name = name;
        this.address = address;
    }
    
    @Override
    public User clone() throws CloneNotSupportedException {
        // Поверхностное копирование самого User
        User cloned = (User) super.clone();
        
        // Но глубокое копирование вложенного объекта
        if (address != null) {
            cloned.address = address.clone();  // Рекурсивное копирование
        }
        
        return cloned;
    }
    
    public Address getAddress() {
        return address;
    }
}

// Глубокое копирование работает правильно
public class DeepCopyCorrect {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("NYC", "10001");
        User user1 = new User("John", address);
        User user2 = user1.clone();  // Deep copy
        
        // Это разные User объекты
        System.out.println(user1 == user2);  // false
        
        // И разные Address объекты!
        System.out.println(user1.getAddress() == user2.getAddress());  // false (правильно!)
        
        // Изменение address в одном НЕ влияет на другой
        user2.getAddress().setCity("Los Angeles");
        
        System.out.println(user1.getAddress().getCity());  // "NYC" (не изменился)
        System.out.println(user2.getAddress().getCity());  // "Los Angeles"
    }
}

Визуально:

После user2 = user1.clone() (deep copy)

user1 ──→ ┌──────────────┐
          │ User object  │
          ├──────────────┤
          │ name: John   │
          │ address  ───┐│
          └──────────────┘│
                          │
                       ┌──▼─────────────────┐
                       │ Address object 1   │
                       ├────────────────────┤
                       │ city: "NYC"        │
                       └────────────────────┘

user2 ──→ ┌──────────────┐
          │ User object  │
          ├──────────────┤
          │ name: John   │
          │ address  ───┐│
          └──────────────┘│
                          │
                       ┌──▼──────────────────────┐
                       │ Address object 2 (КОПИЯ)│
                       ├───────────────────────────┤
                       │ city: "NYC"              │
                       └──────────────────────────┘

Два полностью независимых объекта!

Альтернативные методы копирования

Copy конструктор (современный подход)

public class User {
    private String name;
    private Address address;
    
    // Основной конструктор
    public User(String name, Address address) {
        this.name = name;
        this.address = address;
    }
    
    // Copy конструктор (ЛУЧШЕ, чем clone())
    public User(User other) {
        this.name = other.name;
        this.address = new Address(other.address);  // Глубокое копирование
    }
}

public class Address {
    private String city;
    private String zipCode;
    
    public Address(String city, String zipCode) {
        this.city = city;
        this.zipCode = zipCode;
    }
    
    public Address(Address other) {  // Copy конструктор
        this.city = other.city;
        this.zipCode = other.zipCode;
    }
}

// Использование
User user1 = new User("John", new Address("NYC", "10001"));
User user2 = new User(user1);  // Явное, понятное копирование

Через потоковую сериализацию

import java.io.*;

public class SerializationCopy {
    // Deep copy через сериализацию (медленнее, но простой)
    public static <T extends Serializable> T deepCopy(T original) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(original);
        
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        return (T) ois.readObject();
    }
}

Через MapStruct или ModelMapper (для классов)

// Использование библиотеки MapStruct
@Mapper
public interface UserMapper {
    UserDTO toDTO(User user);  // Копирует (глубоко или поверхностно в зависимости от конфигурации)
}

Сравнение

            Shallow Copy        Deep Copy
─────────────────────────────────────────
Скорость    Быстрое            Медленнее
Память      Меньше памяти      Больше памяти
Проблемы    Общие ссылки       Нет побочных эффектов
Использует  clone()            clone() + рекурсия

Когда какой использовать

Shallow copy:

  • Когда вложенные объекты immutable (String, Integer)
  • Когда не планируешь менять состояние
  • Когда нужна максимальная производительность

Deep copy:

  • Когда вложенные объекты mutable
  • Когда нужна полная независимость объектов
  • В большинстве практических случаев

Вывод

Выбирай deep copy по умолчанию, если не уверен. Shallow copy может привести к трудноловимым багам через общие ссылки на вложенные объекты.