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

Ответ

Поверхностное копирование (Shallow Copy) создает новый объект, но копирует значения всех полей "как есть". Для полей-примитивов копируется значение, для полей-ссылок копируется сама ссылка (адрес памяти). В результате оба объекта начинают ссылаться на одни и те же вложенные объекты.

Глубокое копирование (Deep Copy) создает новый объект и рекурсивно создает копии всех объектов, на которые ссылаются его поля. В итоге получается полностью независимая копия всего графа объектов.

Пример, иллюстрирующий разницу:

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

class Person implements Cloneable {
    String name;
    Address address;

    // Поверхностное клонирование (стандартная реализация)
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // address будет скопирована как ссылка
    }

    // Глубокое клонирование (пользовательская реализация)
    public Person deepCopy() {
        Person copy = new Person();
        copy.name = this.name;
        copy.address = new Address(this.address.city); // Создаем новый объект Address
        return copy;
    }
}

// Использование
Person original = new Person("Alice", new Address("Moscow"));
Person shallowCopy = (Person) original.clone();
Person deepCopy = original.deepCopy();

original.address.city = "SPb";
// shallowCopy.address.city теперь тоже "SPb" (общая ссылка)!
// deepCopy.address.city останется "Moscow" (независимая копия).

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

  • Поверхностное копирование подходит для объектов, содержащих только примитивы или неизменяемые (immutable) ссылочные поля.
  • Глубокое копирование необходимо, если объект содержит изменяемые (mutable) ссылочные поля, и вы хотите обеспечить полную независимость копии.

Ответ 18+ 🔞

А, ну это классика, ёпта! Сейчас объясню на пальцах, без всей этой академической суеты.

Представь, что у тебя есть папка с документами. Поверхностное копирование — это как если ты берешь и ксерокопируешь только обложку этой папки. На копии будет написано то же самое: «Документы Васьки». Но внутри-то, блядь, лежат те же самые листочки! Ты и оригинал, и копия — вы оба тычете пальцем в одну и ту же стопку бумаг. Изменяешь документ в оригинале — он меняется и в «копии», потому что копия-то, сука, только обложка!

А вот глубокое копирование — это уже серьёзно. Ты берешь эту папку, идешь нахуй к ксероксу и начинаешь копировать ВСЁ: и обложку, и каждый листочек внутри, и даже те стикеры, что на листочках налеплены. В итоге получаешь вторую, абсолютно независимую папку. Хоть в оригинальной папке все листы сожги — в копии они останутся целыми и невредимыми.

Смотри на коде, тут всё видно:

// Допустим, есть класс «Адрес», простая ссылочная хуйня.
class Address {
    String city;
    Address(String city) { this.city = city; }
}

// А вот и наш «Человек», который живёт по адресу.
class Person implements Cloneable {
    String name; // Примитив, строка (в Java она immutable, но для примера сойдёт)
    Address address; // А вот это уже ссылка, опасная зона!

    // Стандартный clone() делает ПОВЕРХНОСТНУЮ копию. Лениво, по-быстрому.
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // name скопируется, а address — нет! Скопируется только ссылка на него!
    }

    // А вот это уже наш, ручной, ГЛУБОКОКИЙ метод. Тут мы вручную всё клонируем.
    public Person deepCopy() {
        Person copy = new Person();
        copy.name = this.name; // Имя копируем значение
        // А вот адрес — не копируем ссылку, а создаём НОВЫЙ объект с теми же данными!
        copy.address = new Address(this.address.city);
        return copy;
    }
}

Теперь смотри, как это работает в жизни, блядь:

Person original = new Person("Васька", new Address("Москва"));

// Поверхностная копия (через clone())
Person shallowCopy = (Person) original.clone();
// Глубокая копия (наша самописная)
Person deepCopy = original.deepCopy();

// А теперь Васька решил переехать в Питер!
original.address.city = "Санкт-Петербург";

// И что мы имеем?
System.out.println(shallowCopy.address.city); // Выведет "Санкт-Петербург"! Охуеть!
// Потому что shallowCopy.address — это та же самая ссылка, что и у original. Один и тот же объект в памяти!

System.out.println(deepCopy.address.city); // А вот тут выведет "Москва"!
// Потому что deepCopy.address — это уже новый, отдельный объект. Его Питер не колышет!

Так когда что юзать, чтобы не обосраться?

  • Поверхностную копию можно делать, если твой объект — как чемодан без потайного отделения. Только примитивы (int, boolean) или неизменяемые (immutable) ссылки вроде String (хотя это тоже ссылка, но её не изменишь). Ну или если ты сознательно хочешь, чтобы объекты делились данными.
  • Глубокую копию делай ВСЕГДА, когда у тебя внутри объекта сидят изменяемые (mutable) штуки: списки (List), массивы, другие кастомные объекты. Чтобы твоя копия жила своей жизнью, а не дергалась, как марионетка, когда меняют оригинал.

Короче, если не хочешь неожиданных сюрпризов, когда изменение в одном месте неведомой хуйней ломает всё в другом — думай головой, что копируешь. А то будет тебе, как Герасиму с Муму, блядь.