Клонирование – это процесс создания копии объекта. В классе Java Object есть встроенный метод clone()
, который возвращает копию существующего экземпляра. Поскольку Object является базовым классом в Java, все объекты по умолчанию поддерживают клонирование.
Клонирование объектов в Java
Если вы хотите использовать метод клонирования объекта Java – clone(), вы должны реализовать маркерный интерфейс
java.lang.Cloneable
. В противном случае будет сгенерировано исключение CloneNotSupportedException
во время выполнения. Также следует отметить, что метод clone объекта является защищенным, поэтому вам придется переопределить его. Давайте рассмотрим клонирование объекта в Java на примере программы.
package com.journaldev.cloning;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class Employee implements Cloneable {
private int id;
private String name;
private Map<String, String> props;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, String> getProps() {
return props;
}
public void setProps(Map<String, String> p) {
this.props = p;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Мы используем метод Object clone(), поэтому мы реализовали маркерный интерфейс Cloneable
интерфейс. Вызываем метод клонирования суперкласса, то есть метод Object clone().
Использование метода Object clone()
Создадим тестовую программу для использования метода клонирования объекта для создания копии экземпляра.
package com.journaldev.cloning;
import java.util.HashMap;
import java.util.Map;
public class CloningTest {
public static void main(String[] args) throws CloneNotSupportedException {
Employee emp = new Employee();
emp.setId(1);
emp.setName("Pankaj");
Map props = new HashMap<>();
props.put("salary", "10000");
props.put("city", "Bangalore");
emp.setProps(props);
Employee clonedEmp = (Employee) emp.clone();
// Проверим, являются ли атрибуты emp и clonedEmp одинаковыми или разными
System.out.println("emp and clonedEmp == test: " + (emp == clonedEmp));
System.out.println("emp and clonedEmp HashMap == test: " + (emp.getProps() == clonedEmp.getProps()));
// Посмотрим, каков эффект использования клонирования по умолчанию
// изменение свойств emp
emp.getProps().put("title", "CEO");
emp.getProps().put("city", "New York");
System.out.println("clonedEmp props:" + clonedEmp.getProps());
// изменение имени emp
emp.setName("new");
System.out.println("clonedEmp name:" + clonedEmp.getName());
}
}
Выход:
emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: true
clonedEmp props:{city=New York, salary=10000, title=CEO}
clonedEmp name:Pankaj
CloneNotSupportedException во время выполнения
Если наш класс Employee не реализует интерфейс Cloneable
, то вышеуказанная программа вызовет исключение CloneNotSupportedException
во время выполнения исключение.
Exception in thread "main" java.lang.CloneNotSupportedException: com.journaldev.cloning.Employee
at java.lang.Object.clone(Native Method)
at com.journaldev.cloning.Employee.clone(Employee.java:41)
at com.journaldev.cloning.CloningTest.main(CloningTest.java:19)
Понимание Клонирования Объектов
Давайте рассмотрим выходные данные выше и поймем, что происходит с методом clone()
объекта.
emp и clonedEmp == тест: false
: Это означает, что emp и clonedEmp – два разных объекта, не ссылающихся на один и тот же объект. Это соответствует требованию клонирования объектов в Java.emp и clonedEmp HashMap == тест: true
: Таким образом, переменные объектов emp и clonedEmp ссылаются на один и тот же объект. Это может быть серьезной проблемой целостности данных, если мы изменяем значение базового объекта. Любое изменение значения может отразиться и на клонированном экземпляре.clonedEmp props:{city=New York, salary=10000, title=CEO}
: Мы не вносили никаких изменений в свойства clonedEmp, но они все равно изменились, потому что переменные emp и clonedEmp ссылаются на один и тот же объект. Это происходит потому, что метод clone() по умолчанию для объекта создает поверхностную копию. Это может быть проблемой, когда вы хотите создать полностью отсоединенные объекты с помощью процесса клонирования. Это может привести к нежелательным результатам, поэтому необходимо правильно переопределить метод clone() для объекта.clonedEmp name:Pankaj
: Что произошло здесь? Мы изменили имя emp, но имя clonedEmp не изменилось. Это происходит потому, что String неизменяемый. Поэтому, когда мы устанавливаем имя emp, создается новая строка, и ссылка на имя emp изменяется вthis.name = name;
. Поэтому имя clonedEmp остается неизменным. Вы обнаружите аналогичное поведение и для любых примитивных типов переменных. Так что мы в порядке с клонированием объектов по умолчанию в Java, пока у нас есть только примитивные и неизменяемые переменные в объекте.
Типы клонирования объектов
Есть два типа клонирования объекта – поверхностное клонирование и глубокое клонирование. Давайте разберем каждый из них и выясним, как лучше всего реализовать клонирование в наших программах на Java.
1. Поверхностное клонирование
Реализация по умолчанию метода clone() в Java использует поверхностное копирование. Он использует API reflection для создания копии экземпляра. Ниже приведен фрагмент кода, демонстрирующий реализацию поверхностного клонирования.
@Override
public Object clone() throws CloneNotSupportedException {
Employee e = new Employee();
e.setId(this.id);
e.setName(this.name);
e.setProps(this.props);
return e;
}
2. Глубокое клонирование
При глубоком клонировании нам нужно копировать поля по одному. Если у нас есть поле с вложенными объектами, такими как List, Map и т.д., то нам придется писать код для их копирования по одному. Вот почему это называется глубоким клонированием или глубокой копией. Мы можем переопределить метод clone у Employee, как показано в следующем коде для глубокого клонирования.
public Object clone() throws CloneNotSupportedException {
Object obj = super.clone(); //utilize clone Object method
Employee emp = (Employee) obj;
// глубокое клонирование для неизменяемых полей
emp.setProps(null);
Map hm = new HashMap<>();
String key;
Iterator it = this.props.keySet().iterator();
// Глубокое копирование поля за полем
while (it.hasNext()) {
key = it.next();
hm.put(key, this.props.get(key));
}
emp.setProps(hm);
return emp;
}
С этой реализацией метода clone() наш тестовый программный код выведет следующий результат.
emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: false
clonedEmp props:{city=Bangalore, salary=10000}
clonedEmp name:Pankaj
В большинстве случаев это то, что мы хотим. Метод clone() должен возвращать новый объект, полностью отделенный от исходного экземпляра. Поэтому, если вы думаете использовать метод Object clone() и клонирование в своей программе, делайте это мудро и правильно переопределяйте его, обращая внимание на изменяемые поля. Это может быть трудной задачей, если ваш класс расширяет другой класс, который в свою очередь расширяет другой класс, и так далее. Вам придется пройти все пути в иерархии наследования Object, чтобы позаботиться о глубоком копировании всех изменяемых полей.
Клонирование с использованием сериализации?
Один из способов легко выполнить глубокое клонирование – через сериализацию. Но сериализация – это дорогостоящая процедура, и ваш класс должен реализовывать интерфейс Serializable
. Все поля и суперклассы также должны реализовывать Serializable.
Использование утилит Apache Commons
Если вы уже используете классы утилит Apache Commons в своем проекте, и ваш класс сериализуем, то используйте следующий метод.
Employee clonedEmp = org.apache.commons.lang3.SerializationUtils.clone(emp);
Копирующий конструктор для клонирования
Мы можем определить копирующий конструктор, чтобы создать копию объекта. Зачем вообще полагаться на метод clone() объекта? Например, у нас может быть копирующий конструктор Employee, подобный следующему коду.
public Employee(Employee emp) {
this.setId(emp.getId());
this.setName(emp.getName());
Map hm = new HashMap<>();
String key;
Iterator it = emp.getProps().keySet().iterator();
// Глубокое копирование поля за полем
while (it.hasNext()) {
key = it.next();
hm.put(key, emp.getProps().get(key));
}
this.setProps(hm);
}
Когда нам нужна копия объекта сотрудника, мы можем получить ее, используя Employee clonedEmp = new Employee(emp);
. Однако написание копирующего конструктора может быть утомительной задачей, если в вашем классе много переменных, особенно примитивных и неизменяемых.
Лучшие практики клонирования объектов в Java
-
Используйте метод clone() объекта по умолчанию только тогда, когда в вашем классе присутствуют примитивные и неизменяемые переменные или вам нужна поверхностная копия. В случае наследования вам придется проверить все классы, которые вы расширяете, до уровня Object.
-
Вы также можете определить конструктор копирования, если ваш класс в основном имеет изменяемые свойства.
-
Используйте метод clone() объекта, вызывая
super.clone()
в переопределенном методе клонирования, затем внесите необходимые изменения для глубокого копирования изменяемых полей. -
Если ваш класс сериализуем, вы можете использовать сериализацию для клонирования. Однако это повлечет за собой падение производительности, поэтому перед использованием сериализации для клонирования выполните некоторые тесты производительности.
-
Если вы расширяете класс и он правильно определяет метод клонирования с глубоким копированием, тогда вы можете использовать стандартный метод клонирования. Например, у нас правильно определен метод clone() в классе Employee следующим образом.
@Override public Object clone() throws CloneNotSupportedException { Object obj = super.clone(); Employee emp = (Employee) obj; // глубокое копирование для неизменяемых полей emp.setProps(null); Map<String, String> hm = new HashMap<>(); String key; Iterator<String> it = this.props.keySet().iterator(); // Глубокое копирование поля за полем while (it.hasNext()) { key = it.next(); hm.put(key, this.props.get(key)); } emp.setProps(hm); return emp; }
Мы можем создать дочерний класс и использовать клонирование суперкласса следующим образом.
package com.journaldev.cloning; public class EmployeeWrap extends Employee implements Cloneable { private String title; public String getTitle() { return title; } public void setTitle(String t) { this.title = t; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
Класс
EmployeeWrap
не имеет изменяемых свойств и использует реализацию метода clone() суперкласса. Если есть изменяемые поля, то вам придется позаботиться только о глубоком копировании этих полей. Вот простая программа для проверки, работает ли такое клонирование хорошо или нет.package com.journaldev.cloning; import java.util.HashMap; import java.util.Map; public class CloningTest { public static void main(String[] args) throws CloneNotSupportedException { EmployeeWrap empWrap = new EmployeeWrap(); empWrap.setId(1); empWrap.setName("Pankaj"); empWrap.setTitle("CEO"); Map<String, String> props = new HashMap<>(); props.put("salary", "10000"); props.put("city", "Bangalore"); empWrap.setProps(props); EmployeeWrap clonedEmpWrap = (EmployeeWrap) empWrap.clone(); empWrap.getProps().put("1", "1"); System.out.println("empWrap mutable property value = "+empWrap.getProps()); System.out.println("clonedEmpWrap mutable property value = "+clonedEmpWrap.getProps()); } }
Вывод:
empWrap mutable property value = {1=1, city=Bangalore, salary=10000} clonedEmpWrap mutable property value = {city=Bangalore, salary=10000}
Так что это работает идеально, как мы ожидали.
Это все, что касается клонирования объектов в Java. Надеюсь, у вас есть представление о методе clone() в Java и о том, как его правильно переопределить без нежелательных эффектов.
Вы можете загрузить проект с моего репозитория GitHub.
Source:
https://www.digitalocean.com/community/tutorials/java-clone-object-cloning-java