웹사이트 검색

Java 개체 clone() 메서드 - Java에서 복제


복제는 개체의 복사본을 만드는 프로세스입니다. Java 객체 클래스에는 기존 인스턴스의 복사본을 반환하는 네이티브 clone() 메서드가 함께 제공됩니다. Object는 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() 메서드를 호출하고 있습니다.

객체 clone() 메서드 사용

객체 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 and clonedEmp == test: false: 이는 emp와 clonedEmp가 동일한 객체를 참조하는 것이 아니라 서로 다른 두 객체임을 의미합니다. 이것은 Java 개체 복제 요구 사항과 일치합니다.
  2. emp 및 clonedEmp HashMap == 테스트: 참: 따라서 emp 및 clonedEmp 개체 변수는 모두 동일한 개체를 참조합니다. 기본 개체 값을 변경하면 심각한 데이터 무결성 문제가 될 수 있습니다. 값이 변경되면 복제된 인스턴스에도 반영될 수 있습니다.
  3. clonedEmp props:{city=New York, salary=10000, title=CEO}: 우리는 clonedEmp 속성을 변경하지 않았지만 여전히 변경되었습니다. 변수는 동일한 객체를 참조합니다. 이것은 기본 Object clone() 메서드가 얕은 복사본을 생성하기 때문에 발생합니다. 복제 프로세스를 통해 완전히 분리된 개체를 생성하려는 경우 문제가 될 수 있습니다. 이로 인해 원하지 않는 결과가 발생할 수 있으므로 Object clone() 메서드를 적절하게 재정의해야 합니다.
  4. clonedEmp name:Pankaj: 여기서 무슨 일이 일어났나요?emp 이름을 변경했지만 clonedEmp 이름은 변경되지 않았습니다. String은 불변이기 때문입니다. 따라서 emp 이름을 설정할 때 새 문자열이 생성되고 this.name = name;에서 emp 이름 참조가 변경됩니다. 따라서 clonedEmp 이름은 변경되지 않습니다. 기본 변수 유형에 대해서도 유사한 동작을 찾을 수 있습니다. 따라서 개체에 기본 변수와 불변 변수만 있는 한 Java 개체 기본 복제를 잘 수행할 수 있습니다.

개체 복제 유형

객체 복제에는 얕은 복제와 심층 복제의 두 가지 유형이 있습니다. 각각을 이해하고 Java 프로그램에서 복제를 구현하는 가장 좋은 방법을 찾아봅시다.

1. 얕은 복제

Java 개체 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. 딥 클로닝

딥 클로닝에서는 필드를 하나씩 복사해야 합니다. List, Map 등과 같은 중첩된 개체가 있는 필드가 있는 경우에도 하나씩 복사하는 코드를 작성해야 합니다. 그래서 딥 클로닝 또는 딥 카피라고 합니다. 깊은 복제를 위해 다음 코드와 같이 Employee 복제 메서드를 재정의할 수 있습니다.

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() 메서드는 원래 인스턴스에서 완전히 분리된 새 개체를 반환해야 합니다. 따라서 프로그램에서 개체 복제 및 복제를 사용하려는 경우 현명하게 수행하고 변경 가능한 필드를 관리하여 적절하게 재정의하십시오. 클래스가 다른 클래스를 확장하고 다른 클래스를 확장하는 등의 경우 어려운 작업이 될 수 있습니다. 변경 가능한 모든 필드의 딥 카피를 관리하려면 개체 상속 계층 구조 전체로 이동해야 합니다.

직렬화를 사용한 복제?

딥 클로닝을 쉽게 수행하는 한 가지 방법은 직렬화를 이용하는 것입니다. 그러나 직렬화는 비용이 많이 드는 절차이며 클래스는 Serializable 인터페이스를 구현해야 합니다. 모든 필드와 슈퍼클래스도 Serializable을 구현해야 합니다.

Apache Commons Util 사용

프로젝트에서 이미 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 Object clone() 메서드와 역효과 없이 올바르게 재정의하는 방법에 대한 아이디어를 얻으셨기를 바랍니다.

내 GitHub 저장소에서 프로젝트를 다운로드할 수 있습니다.

참조: 객체 복제용 API 문서