Подтвердить что ты не робот

Глубокая копия, мелкая копия, клон

Возможный дубликат:
Как скопировать объект в Java?

Мне нужно уточнить различия между глубокой копией, неглубокой копией и клоном в Java

4b9b3361

Ответ 1

К сожалению, "мелкая копия", "глубокая копия" и "клон" - все довольно неопределенные термины.


В контексте Java сначала нужно провести различие между "копированием значения" и "копированием объекта".

int a = 1;
int b = a;     // copying a value
int[] s = new int[]{42};
int[] t = s;   // copying a value (the object reference for the array above)

StringBuffer sb = new StringBuffer("Hi mom");
               // copying an object.
StringBuffer sb2 = new StringBuffer(sb);

Короче говоря, назначение ссылки на переменную, тип которой является ссылочным типом, - это "копирование значения", где это значение является ссылкой на объект. Чтобы скопировать объект, что-то нужно использовать new, явно или под капотом.


Теперь для "мелкого" и "глубокого" копирования объектов. Мелкое копирование обычно означает копирование только одного уровня объекта, в то время как глубокое копирование обычно означает копирование более одного уровня. Проблема состоит в том, чтобы решить, что мы подразумеваем под уровнем. Рассмотрим это:

public class Example {
    public int foo;
    public int[] bar;
    public Example() { };
    public Example(int foo, int[] bar) { this.foo = foo; this.bar = bar; };
}

Example eg1 = new Example(1, new int[]{1, 2});
Example eg2 = ... 

Нормальная интерпретация заключается в том, что "мелкой" копией eg1 будет новый объект Example, чей foo равен 1 и чье поле bar относится к тому же массиву, что и в оригинале; например.

Example eg2 = new Example(eg1.foo, eg1.bar);

Нормальная интерпретация "глубокой" копии eg1 будет новым объектом Example, чей foo равен 1 и чье поле bar относится к копии исходного массива; например.

Example eg2 = new Example(eg1.foo, Arrays.copy(eg1.bar));

(Люди, идущие с фона C/С++, могут сказать, что ссылочное задание создает мелкую копию. Однако это не то, что мы обычно подразумеваем под мелким копированием в контексте Java...)

Существуют еще два вопроса/области неопределенности:

  • Насколько глубоко глубока? Он останавливается на двух уровнях? Три уровня? Означает ли это весь график связанных объектов?

  • Как насчет инкапсулированных типов данных; например строка? Строка на самом деле не только один объект. Фактически, это "объект" с некоторыми скалярными полями и ссылка на массив символов. Однако массив символов полностью скрыт API. Итак, когда мы говорим о копировании String, имеет ли смысл называть это "мелкой" копией или "глубокой" копией? Или мы просто назовем это копией?


Наконец, клон. Клонирование - это метод, который существует для всех классов (и массивов), которые, как считается, создают копию целевого объекта. Однако:

  • Спецификация этого метода сознательно не говорит о том, является ли это мелкой или глубокой копией (при условии, что это значимое различие).

  • Фактически, спецификация даже не указывает, что клон создает новый объект.

Здесь javadoc говорит:

"Создает и возвращает копию этого объекта. Точный смысл" копии "может зависеть от класса объекта. Общее намерение состоит в том, что для любого объекта x выражение x.clone() != x будет истинным, а что выражение x.clone().getClass() == x.getClass() будет истинным, но это не абсолютные требования. Хотя обычно бывает, что x.clone().equals(x) будет истинным, это не является абсолютным требованием."

Обратите внимание, что это говорит о том, что в какой-то крайности клон может быть целевым объектом, а с другой стороны клон может не совпадать с оригиналом. И это предполагает, что клон поддерживается даже.

Короче говоря, клон потенциально означает что-то другое для каждого класса Java.


Некоторые люди утверждают (как @supercat делает в комментариях), что метод Java clone() нарушен. Но я считаю, что правильный вывод состоит в том, что концепция клонирования нарушается в контексте ОО. AFAIK, невозможно разработать единую модель клонирования, которая согласована и применима для всех типов объектов.

Ответ 2

Термин "клон" неоднозначен (хотя библиотека классов Java включает в себя Cloneable интерфейс) и может ссылаться на глубокую копию или мелкой копии. Глубокие/мелкие копии специально не привязаны к Java, а представляют собой общую концепцию, связанную с созданием копии объекта, и ссылаются на то, как копируются и объекты объекта.

В качестве примера предположим, что у вас есть класс человека:

class Person {
    String name;
    List<String> emailAddresses
}

Как вы клонируете объекты этого класса? Если вы выполняете мелкую копию, вы можете скопировать имя и поместить ссылку на emailAddresses в новый объект. Но если вы изменили содержимое списка emailAddresses, вы бы изменили список в обеих копиях (с тех пор как работают ссылки на объекты).

Глубокая копия будет означать, что вы рекурсивно скопируете каждый элемент, поэтому вам нужно будет создать новый List для нового Person, а затем скопировать содержимое из старого в новый объект.

Хотя приведенный выше пример тривиален, различия между глубокими и неглубокими копиями значительны и оказывают серьезное влияние на любое приложение, особенно если вы пытаетесь заранее разработать общий метод клонирования, не зная, как кто-то может его использовать позже, Есть моменты, когда вам нужна глубокая или неглубокая семантика, или какой-то гибрид, где вы глубоко копируете некоторые члены, но не другие.

Ответ 3

  • Глубокая копия: клонируйте этот объект и каждую ссылку на все остальные объекты, которые он имеет
  • Неверная копия: клонируйте этот объект и сохраните его ссылки
  • Object clone() throws CloneNotSupportedException: не указано, должно ли оно возвращать глубокую или мелкую копию, но по крайней мере: o.clone()!= o

Ответ 4

A мелкая копия - не что иное, как другая ссылка на существующий объект. Любые изменения, внесенные в мелкую копию, видны (и ощущаются) другими ссылками на этот объект. Рассмотрим следующий код:

    List<Integer> listA, listB;
    listA = new ArrayList<>();
    listA.add(new Integer(1));

    listB = listA;
    listB.add(new Integer(2));

Оператор: listB = listA делает listB "неполной копией" списка A, и обе переменные ссылаются на тот же список. После утверждения: listB.add(новый Integer (2));, listB (более конкретно, список, содержащий списокB-ссылок) содержит два элемента (1 и 2). Однако, поскольку listA также ссылается на тот же список, он также содержит те же два элемента.

В Java нет точного определения для глубокой копии. Существует интерфейс, который позволяет разработчику определить, что означает клон, но это зависит от прихотей разработчика. Глубокая копия и клон подразумевают, что вместо ссылки на объект вы получаете совершенно отдельный, отдельный объект.

Рассмотрим приведенный выше пример кода. С глубокой копией (или клоном) listA и listB будут отличаться друг от друга. (То есть, они будут ссылаться на разные предметы). Таким образом, для глубокой копии или клона listB.add() не повлияет на список listA. То есть, listA все равно будет иметь один элемент (1), а listB будет иметь два элемента (1 и 2).

Увы, это может быть или не быть. Существует интерфейс (Cloneable), который указывает способ создания клона объекта Cloneable. Насколько глубоко это происходит, зависит от того, что команда, которая определила и внедрила класс, представляет собой клон. Решение о том, что делать глубокие или мелкие копии на объекте, не является тривиальным решением. Есть некоторые объекты, где глубокая копия не имеет смысла (например, ссылка на одноэлементный) или неэффективна (например, копия константы, например, String).

См. fooobar.com/questions/30104/... или этот.

Ответ 5

Термины "мелкая копия" и "глубокая копия" немного расплывчаты; Я бы предложил использовать термины "memberwise clone" и то, что я бы назвал "семантическим клоном". "Помещенный клон" объекта - это новый объект с тем же временем выполнения, что и исходный, для каждого поля, система эффективно выполняет "newObject.field = oldObject.field". Базовый объект Object.Clone() выполняет членский клон; членное клонирование, как правило, является правой начальной точкой для клонирования объекта, но в большинстве случаев потребуется некоторая "работа по исправлению" после членного клонирования. Во многих случаях попытка использовать объект, созданный с помощью поэтапного клона без предварительного выполнения необходимого исправления, приведет к возникновению плохих вещей, включая повреждение клонированного объекта и, возможно, других объектов. Некоторые люди используют термин "мелкое клонирование" для обозначения клонирования по порядку, но это не единственное использование термина.

"Семантический клон" - это объект, который содержит те же данные, что и оригинал, с точки зрения типа. Для проверки рассмотрим BigList, который содержит Array > и count. Клон семантического уровня такого объекта выполнял бы членный клон, затем заменял Array > новым массивом, создавал новые вложенные массивы и копировал все T из исходных массивов в новые. Он не будет пытаться выполнить какое-либо глубокое клонирование самих T. По иронии судьбы, некоторые люди ссылаются на клонирование "мелкого клонирования", в то время как другие называют это "глубоким клонированием". Не совсем полезная терминология.

Хотя бывают случаи, когда действительно глубокое клонирование (рекурсивное копирование всех изменяемых типов) полезно, оно должно выполняться только типами, составляющие которых предназначены для такой архитектуры. Во многих случаях поистине глубокое клонирование является чрезмерным, и оно может мешать ситуациям, когда то, что необходимо, фактически является объектом, чье видимое содержимое относится к тем же объектам, что и другое (то есть копия семантического уровня). В случаях, когда видимое содержимое объекта рекурсивно выводится из других объектов, клон семантического уровня будет подразумевать рекурсивный глубокий клон, но в тех случаях, когда видимое содержимое является всего лишь одним общим типом, код не должен слепо глубоко клонировать все похоже, что он может быть глубоко-клонированным.