что в java можно клонировать
Глубокое погружение в клонирование
Прежде чем мы продолжим концепцию клонирования, давайте освежим наши основы концепцией создания объекта. Когда объекты создаются с использованием оператора new, объекты получают выделение памяти в куче.
Создание объекта в куче
В Java в идеале объекты модифицируются только через ссылочную переменную, т.е. копируется только адрес памяти объекта, и, следовательно, любые изменения в исходном объекте будут отражаться в новой переменной.
Glass objGlass1 = новое стекло ();
Стекло objGlass2 = objGlass1;
Здесь, в этом случае любые изменения, которые вы вносите в объект objGlass1, будут отражаться в объекте objGlass2 и наоборот. Это означает, что ‘ objGlass1 == objGlass2 ‘ вернет true, и ссылочные переменные objGlass1 и objGlass2 ссылаются на один и тот же объект. Однако, если вы намереваетесь скопировать объект, в отличие от простого копирования ссылки на объект, вам понадобится клонирование.
Что такое клонирование?
Клонирование — это процесс копирования объекта, то есть создания нового экземпляра путем копирования самого себя. Клонирование в Java может быть выполнено с помощью метода clone () объекта.
Клонирование создает и возвращает копию объекта с тем же классом и со всеми полями, имеющими одинаковые значения.
Glass objGlass1 = новое стекло ();
Glass objGlass2 = (Стекло) objGlass.clone ();
Давайте посмотрим на анализ ниже после клонирования:
Мелкое клонирование против глубокого клонирования
Java поддерживает два типа клонирования — мелкое клонирование и глубокое клонирование.
В случае мелкого клонирования создается новый объект, который имеет точную копию значений в исходном объекте. Метод clone () объекта обеспечивает поверхностное клонирование. В этом механизме клонирования объект копируется без содержащихся в нем объектов.
Мелкий клон копирует только структуру верхнего уровня объекта, а не нижние уровни.
Методы объектов Java: clone()
Вступление
Эта статья является продолжением серии статей, описывающих часто забываемые методы базового класса объектов языка Java. Ниже приведены методы базового объекта Java, которые присутствуют во всех объектах Java из-за неявного наследования объекта.
Почему существует необходимость клонировать() объект
Сначала я хотел бы начать с того, почему в первую очередь может потребоваться создать клон или копию объекта. Я снова буду использовать свой класс Person из предыдущих статей этой серии для демонстраций, особенно важно то, что это изменяемая его версия, иначе копирование было бы спорным вопросом.
Как только что стало очевидно, присвоение ссылочных типов обрабатывается иначе, чем, скажем, целое число или, возможно, более точно указано int на языке Java. Когда вы назначаете ссылочную переменную другой ссылочной переменной, вы просто указываете ей местоположение, в котором на этот объект можно ссылаться в памяти, что сильно отличается от фактического копирования содержимого, которое происходит, когда вы делаете то же самое с типами значений.
Как клонировать() объект
Для простого класса, такого как Person, который не содержит никаких изменяемых полей, все, что требуется для создания клона, – это вернуть вызов метода клонирования объекта базового класса, например:
В этом примере создание клона человека довольно просто и выполняется примерно так:
К сожалению, хотя эта реализация метода clone() будет работать только с простым типом значений, содержащим объекты, которые не имеют изменяемых ссылочных свойств. Если бы я добавил пару изменяемых полей, таких как мать типа Человек и семья массив Человек объектов, мне нужно было бы внести несколько изменений, чтобы обеспечить безопасное клонирование.
Чтобы продемонстрировать это, мне нужно обновить свой Человек класс вот так.
Если бы я специально не потратил время на индивидуальное создание клонов этих изменяемых полей, то два результирующих объекта Person ссылались бы на одни и те же мать и семья изменяемые экземпляры объектов, что было бы ужасным беспорядком для отладки в будущем. Это явное копирование изменяемых элементов объекта из поля в поле называется глубоким копированием.
Альтернативные методы создания копий экземпляров
Я видел несколько других способов создания клонов объектов, в которых используются такие методы, как сериализация, конструкторы копирования и фабричные методы, создающие копии объектов. Однако в этом разделе я собираюсь рассмотреть только последние два, потому что лично мне не очень нравится использование сериализации для создания копий объектов.
Git Essentials
Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!
В теле конструктора копирования каждому полю копируемого объекта либо непосредственно присваивается новый экземпляр этого класса в случае типов значений, либо используется для создания новых экземпляров их полей в случае ссылочных типов.
Вот пример использования конструктора копирования для класса Person :
Другая техника, которую я покажу, использует фабричный метод. Метод фабричного метода по сути аналогичен конструктору копирования, за исключением того, что новая копия создается внутри статического фабричного метода, который возвращает новый экземпляр в виде копии, например:
Сравнение различий в реализации
Вывод
Как всегда, спасибо за чтение и не стесняйтесь комментировать или критиковать ниже.
Метод clone, интерфейс Cloneable
— Я тебе расскажу про метод clone().
Цель этого метода – клонировать объект – т.е. создать его клон/копию/дубликат.
Если его вызвать, то Java-машина создаст и вернет дубликат объекта, у которого вызвали этот метод.
Клонирование объекта в классе Object реализовано очень примитивно – при клонировании создается всего один новый объект: просто создается еще один объект и его полям присваиваются значения полей объекта-образца.
Если копируемый объект содержит ссылки на другие объекты, то ссылки будут скопированы, дубликаты тех объектов не создаются.
— Дело в том, что Java-машина не знает, какие объекты можно клонировать, а какие нет. Файлы, например, клонировать нельзя. Как и поток System.in.
Поэтому вопрос о полноценном клонировании был отдан на откуп разработчикам классов. Тут все было сделано по аналогии с методом equals. Даже есть свой аналог hashCode – это интерфейс Cloneable.
Интерфейс Cloneable – это так называемый интерфейс-маркер, который не содержит никаких методов. Он используется, чтобы маркировать (помечать) некоторые классы.
Если разработчик класса считает, что объекты класса можно клонировать, он помечает класс этим интерфейсом (наследует класс от Cloneable).
Если разработчика не устраивает стандартная реализация метода clone, он должен написать свою, которая будет создавать дубликат объекта правильным образом.
При вызове метода clone(), Java проверяет, был ли у объекта интерфейс Cloneable. Если да — клонирует объект методом clone(), если нет — выкидывает исключение CloneNotSupportedException.
— Т.е. мы должны или переопределить метод clone и написать его новую реализацию или унаследовать класс от Cloneable?
— Да, но переопределять метод все же придется. Метод clone() объявлен как protected, так что он доступен для вызова только классам из его пакета (java.lang.*) или классам-наследникам.
— Я немного запутался, так что же надо сделать, чтобы клонировать объект?
— Смотри, если ты хочешь воспользоваться «клонированием по умолчанию», которое реализовано в классе Object, тебе нужно:
а) Добавить интерфейс Cloneable своему классу
б) Переопределить метод clone и вызвать в нем базовую реализацию:
Или ты можешь написать реализацию метода clone полностью сам:
— Интересный метод, буду пользоваться. Иногда…
КМБ. Некоторые особенности клонирования объектов в Java
Эта статья рассчитана на новичков или людей, которые слышали о данной технологии, но никогда не имели с ней дело. В русскоязычном интернете, по моему мнению, этот вопрос раскрыт недостаточно хорошо, поэтому задачей этой статьи является максимально осветить вопросы клонирования и методы их реализации. В данной статье будут рассмотрены поверхностное и глубокое клонирование сложных объектов.
Начнем с примера. Предположим, у нас есть некий класс, который хранит в себе переменную типа Integer:
public class SomeProperty
<
private Integer a = null;
public SomeProperty(int value)
<
setA(new Integer(value));
>
public void setA(Integer a)
<
this.a = a;
>
public Integer getA()
<
return a;
>
>
Поскольку в Java все сложные объекты передаются в методы по ссылке, а при присваивании объектов мы присваиваем их указатели, то после строк:
SomeProperty a = new SomeProperty (1);
SomeProperty b = new SomeProperty (2);
b = a;
и а, и b будут указывать на a. Соответственно, при изменении внутреннего состоянии объекта а, поменяется и внутреннее состояние объекта b.
Проблемы, возникающие присваивания ссылок, а не значений происходит и в случае:
SomeProperty prop = new SomeProperty(2);
List a = new ArrayList();
a.add(prop);
List b = new ArrayList(a);
b.get(0).setA(3);
При этом и a, и b будут иметь одно и то же значение.
Отдельно следует рассмотреть случай с List.
Integer c = 2;
List a = new ArrayList();
a.add©;
List b = new ArrayList(a);
c = b.get(0);
c = 3;
в обоих списках лежат ссылки на один и тот же объект с, однако после строки c = 3, значение в списках не меняются, т.к. Integer – лишь декоратор над простым типом int, и при присваивании мы создаем новый объект:
Итак, если нам нужно произвести какие-либо действия с внутренним состоянием сложного объекта, при этом сохранив его начальную копию для дальнейших манипуляций, прибегают к клонированию объектов.
Как результат – мы будем иметь два объекта с идентичным состоянием, но при изменении состояния одного из них, второй останется неизменным.
Небольшие выжимки из теории:
Чтобы объект можно было клонировать, он должен реализовывать интерфейс Сloneable. Использование интерфейса влияет на поведение метода (clone) родительского класса (Object). Таким образом, вызов метода SomeProperty.clone() создаст новый объект SomeProperty.
Глубокое клонирование подразумевает клонирование и ссылочных, и обыкновенных полей (клонировать объекты, на которые они ссылаются) объекта.
Для использования механизма клонирования возможны 2 варианта: переопределение метода clone(), не переопределять метод clone() — в этом случае клонированием будет заниматься JVM. При вызове этого метода копирование выполняется на уровне виртуальной машины без вызова конструктора нового объекта. Значения всех полей копируются.
Самое важное в процессе клонирования – запомнить, что если у вас сложный глобальный объект, то все те внутренние объекты, которые он инкапсулирует, будут присваиваться по ссылке, не клонироваться! То есть по умолчанию в Java реализуется поверхностное клонирование объектов.
public class SomeProperty implements Cloneable
<
private int a;
private SomeData someData = null;
public Object clone()
<
Object result = null;
try
<
result = super.clone();
> catch (CloneNotSupportedException e)
<
e.printStackTrace();
>
return result;
>
public SomeProperty(final int value, final SomeData prop)
<
setA(value);
setSomeData(prop);
>
public void setA(int a)
<
this.a = a;
>
public int getA()
<
return a;
>
public void setSomeData(SomeData someData)
<
this.someData = someData;
>
public SomeData getSomeData()
<
return someData;
>
public class SomeData implements Cloneable
<
public Object clone()
<
Object result = null;
try
<
result = super.clone();
> catch (CloneNotSupportedException e)
<
e.printStackTrace();
>
return result;
>
private int someInt;
public SomeData(final int data)
<
someInt = data;
>
public void setSomeInt(int someInt)
<
this.someInt = someInt;
>
public int getSomeInt()
<
return someInt;
>
>
После строк:
SomeData innerData = new SomeData(5);
SomeProperty firstProperty = new SomeProperty(2, innerData);
SomeProperty secondProperty = (SomeProperty) firstProperty.clone();
Если бы мы вызвали innerData.setSomeInt(10), то значение поменялось бы и в классе firstProperty, и в классе secondProperty (аналогично примерам, описанным выше для ссылочных полей).
Если же мы перепишем метод SomeProperty.clone() и вручную создадим объект класса SomeData, данные и внутренне состояние будут несвязанны:
public Object clone()
<
Object result = null;
try
<
result = super.clone();
((SomeProperty)result).setSomeData((SomeData) someData.clone());
> catch (CloneNotSupportedException e)
<
e.printStackTrace();
>
return result;
>
Все хорошо, все работает, но ровно до тех пор, пока мы не захотим реализовать какую-либо рекуррентную структуру: композит, дерево, список.
public class SomeProperty implements Cloneable
<
private int a;
private SomeProperty someProperty = null;
В данном классе строчка ((SomeProperty)result).setSomeProperty((SomeProperty) someProperty.clone());
будет вызывать переполнение буфера виртуальной машины, так как если мы попытаемся создать двусвязный список:
SomeProperty innerProperty = new SomeProperty(5, null);
SomeProperty firstProperty = new SomeProperty(2, null);
innerProperty.setSomeProperty(firstProperty);
firstProperty.setSomeProperty(innerProperty);
SomeProperty secondProperty = (SomeProperty) firstProperty.clone();
Мы не сможем корректно клонировать объекты из-за их двусторонней связности.
В этом случае глубокое клонирование реализовывается через сериализацию. При сериализации связи между объектами «разрываются», т.к. вложенные объекты сохраняются по значению, а не ссылке:
public Object clone() throws <
SomeProperty obj = (SomeProperty) super.clone();
Object object = null;
try <
object = getDeepCloning(obj);
> catch (Exception e) <
e.printStackTrace();
>
return object;
>
public Object getDeepCloning(Object obj) <
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
>
Надеюсь, новичкам эта статья окажется полезной.
Конструктор копирования Java
Вот как создавать конструкторы копирования в Java и почему реализация Cloneable-не такая уж отличная идея.
1. введение
Это полезно, когда мы хотим скопировать сложный объект, который имеет несколько полей, или когда мы хотим сделать глубокую копию существующего объекта.
2. Как создать конструктор копирования
Чтобы создать конструктор копирования, мы можем сначала объявить конструктор, который принимает объект того же типа в качестве параметра:
Затем мы копируем каждое поле входного объекта в новый экземпляр:
Если класс Java имеет изменяемые поля, то мы можем вместо этого сделать глубокую копию внутри его конструктора копирования. При глубокой копии вновь созданный объект не зависит от исходного, потому что мы создаем отдельную копию каждого изменяемого объекта:
3. Конструктор копирования против Клон
В Java мы также можем использовать метод clone для создания объекта из существующего объекта. Однако конструктор копирования имеет некоторые преимущества перед методом clone :
4. Вопросы наследования
Конструкторы копирования в Java не наследуются подклассами. Поэтому, если мы попытаемся инициализировать дочерний объект из ссылки на родительский класс, мы столкнемся с проблемой приведения при клонировании его с помощью конструктора копирования.
Чтобы проиллюстрировать эту проблему, давайте сначала создадим подкласс Employee и его конструктор копирования:
Затем мы объявляем переменную Employee и создаем ее экземпляр с помощью конструктора Manager :
Мы можем получить ClassCastException во время выполнения, если входной объект не является экземпляром Manager class.
Один из способов избежать приведения в конструктор копирования – создать новый наследуемый метод для обоих классов:
5. Заключение
Конструктор копирования имеет проблему приведения, когда мы используем его для клонирования объекта дочернего класса, ссылочным типом которого является родительский класс. Мы предоставили одно решение для этого вопроса.