Поиск по сайту:

Метод клонирования объекта Java() - клонирование в Java


Клонирование — это процесс создания копии объекта. Класс Java Object поставляется со встроенным методом clone(), который возвращает копию существующего экземпляра. Поскольку Object является базовым классом в Java, все объекты по умолчанию поддерживают клонирование.

Клонирование объектов 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. Мы вызываем метод clone() суперкласса, то есть метод 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<String, String> props = new HashMap<>();
		props.put("salary", "10000");
		props.put("city", "Bangalore");
		emp.setProps(props);

		Employee clonedEmp = (Employee) emp.clone();

		// Check whether the emp and clonedEmp attributes are same or different
		System.out.println("emp and clonedEmp == test: " + (emp == clonedEmp));
		
		System.out.println("emp and clonedEmp HashMap == test: " + (emp.getProps() == clonedEmp.getProps()));
		
		// Let's see the effect of using default cloning
		
		// change emp props
		emp.getProps().put("title", "CEO");
		emp.getProps().put("city", "New York");
		System.out.println("clonedEmp props:" + clonedEmp.getProps());

		// change emp name
		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)

Общие сведения о клонировании объектов

Давайте посмотрим на приведенный выше вывод и поймем, что происходит с методом Object clone().

  1. emp и clonedEmp == test: false: это означает, что emp и clonedEmp — два разных объекта, не относящиеся к одному и тому же объекту. Это соответствует требованию клонирования объектов Java.
  2. emp и clonedEmp HashMap == test: true: Таким образом, объектные переменные emp и clonedEmp ссылаются на один и тот же объект. Это может стать серьезной проблемой целостности данных, если мы изменим базовое значение объекта. Любое изменение значения может отразиться и на клонированном экземпляре.
  3. clonedEmp props:{city=Нью-Йорк, зарплата=10000, title=CEO}: мы не вносили никаких изменений в свойства clonedEmp, но тем не менее они были изменены, потому что и emp, и clonedEmp переменные ссылаются на один и тот же объект. Это происходит потому, что метод Object clone() по умолчанию создает неглубокую копию. Это может быть проблемой, если вы хотите создать полностью отдельные объекты в процессе клонирования. Это может привести к нежелательным результатам, поэтому необходимо правильно переопределить метод Object clone().
  4. clonedEmp name: Pankaj: Что здесь произошло? Мы изменили имя emp, но имя clonedEmp не изменилось. Это потому, что String неизменяем. Поэтому, когда мы устанавливаем имя emp, создается новая строка, и ссылка на имя emp изменяется в this.name=name;. Следовательно, имя clonedEmp остается неизменным. Аналогичное поведение вы найдете и для любых примитивных типов переменных. Таким образом, у нас хорошо получается клонировать объект Java по умолчанию, если у нас есть только примитивные и неизменяемые переменные в объекте.

Типы клонирования объектов

Существует два типа клонирования объектов — поверхностное клонирование и глубокое клонирование. Давайте разберемся с каждым из них и выясним, как лучше всего реализовать клонирование в наших программах на Java.

1. Поверхностное клонирование

Реализация метода Java Object clone() по умолчанию использует неглубокое копирование. Он использует API отражения для создания копии экземпляра. Приведенный ниже фрагмент кода демонстрирует реализацию неглубокого клонирования.

@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. Глубокое клонирование

При глубоком клонировании мы должны копировать поля одно за другим. Если у нас есть поле с вложенными объектами, такими как список, карта и т. д., то мы должны написать код, чтобы скопировать их тоже один за другим. Вот почему это называется глубоким клонированием или глубоким копированием. Мы можем переопределить метод клонирования сотрудников, например следующий код для глубокого клонирования.

public Object clone() throws CloneNotSupportedException {

	Object obj = super.clone(); //utilize clone Object method

	Employee emp = (Employee) obj;

	// deep cloning for immutable fields
	emp.setProps(null);
	Map<String, String> hm = new HashMap<>();
	String key;
	Iterator<String> it = this.props.keySet().iterator();
	// Deep Copy of field by field
	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 и клонирование в своей программе, делайте это с умом и переопределяйте его должным образом, заботясь об изменяемых полях. Это может быть сложной задачей, если ваш класс расширяет другой класс, который, в свою очередь, расширяет другой класс и так далее. Вам придется пройти весь путь в иерархии наследования объектов, чтобы позаботиться о глубоком копировании всех изменяемых полей.

Клонирование с помощью сериализации?

Одним из способов простого выполнения глубокого клонирования является сериализация. Но сериализация — дорогостоящая процедура, и ваш класс должен реализовать интерфейс Serializable. Все поля и суперклассы также должны реализовывать Serializable.

Использование утилиты Apache Commons

Если вы уже используете классы Apache Commons Util в своем проекте и ваш класс сериализуем, используйте метод, описанный ниже.

Employee clonedEmp = org.apache.commons.lang3.SerializationUtils.clone(emp);

Конструктор копирования для клонирования

Мы можем определить конструктор копирования для создания копии объекта. Зачем вообще зависеть от метода Object clone()? Например, у нас может быть конструктор копирования Employee, подобный следующему коду.

public Employee(Employee emp) {
	
	this.setId(emp.getId());
	this.setName(emp.getName());
	
	Map<String, String> hm = new HashMap<>();
	String key;
	Iterator<String> it = emp.getProps().keySet().iterator();
	// Deep Copy of field by field
	while (it.hasNext()) {
		key = it.next();
		hm.put(key, emp.getProps().get(key));
	}
	this.setProps(hm);

}

Всякий раз, когда нам нужна копия объекта сотрудника, мы можем получить его, используя Employee clonedEmp=new Employee(emp);. Однако написание конструктора копирования может быть утомительной работой, если в вашем классе много переменных, особенно примитивных и неизменяемых.

Рекомендации по клонированию объектов Java

  1. Use default Object clone() method only when your class has primitives and immutable variables or you want shallow copy. In case of inheritance, you will have to check all the classes you are extending till the Object level.

  2. You can also define copy constructor if your class has mostly mutable properties.

  3. Utilize Object clone() method by calling super.clone() in overridden clone method, then make necessary changes for deep copying of mutable fields.

  4. If your class is serializable, you can use serialization for cloning. However, it will come with a performance hit, so do some benchmarking before using serialization for cloning.

  5. If you are extending a class and it has defined clone method properly using deep copy, then you can utilize default clone method. For example, we have properly defined clone() method in Employee class as follows.

    @Override
    public Object clone() throws CloneNotSupportedException {
    
    	Object obj = super.clone();
    
    	Employee emp = (Employee) obj;
    
    	// deep cloning for immutable fields
    	emp.setProps(null);
    	Map<String, String> hm = new HashMap<>();
    	String key;
    	Iterator<String> it = this.props.keySet().iterator();
    	// Deep Copy of field by field
    	while (it.hasNext()) {
    		key = it.next();
    		hm.put(key, this.props.get(key));
    	}
    	emp.setProps(hm);
    
    	return emp;
    }
    

    We can create a child class and utilize the superclass deep cloning as follows.

    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();
    	}
    }
    

    The EmployeeWrap class doesn’t have any mutable properties and it’s utilizing superclass clone() method implementation. If there are mutable fields, then you will have to take care of deep copying of only those fields. Here is a simple program to test if this way of cloning works fine or not.

    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());
    		
    	}
    
    }
    

    Output:

    empWrap mutable property value = {1=1, city=Bangalore, salary=10000}
    clonedEmpWrap mutable property value = {city=Bangalore, salary=10000}
    

    So it worked perfectly as we expected.

Это все о клонировании объектов в java. Надеюсь, у вас есть некоторое представление о методе clone() объекта Java и о том, как правильно переопределить его без каких-либо побочных эффектов.

Вы можете скачать проект из моего репозитория GitHub.

Ссылка: API Doc for Object clone