Java의 객체 복제 메서드 활용

복제란 무엇인가?

프로그래밍 실무에서 종종 다음과 같은 상황이 발생한다: 특정 시점에 객체 A가 여러 유효한 데이터를 포함하고 있을 때, A와 동일한 상태를 가진 새로운 객체 B를 생성하고 싶은 경우가 있다. 이후 B에 대한 변경 사항은 A에 영향을 주지 않도록 해야 하며, 이는 두 객체가 완전히 독립적임을 의미한다. 단순한 대입 연산만으로는 이러한 요구사항을 충족할 수 없다. 다양한 방법이 있지만, clone() 메서드를 구현하는 것이 가장 간단하고 효율적인 접근 방식이다.

자바에서는 모든 클래스가 java.lang.Object 클래스를 상속받으며, 이 클래스에는 clone() 메서드가 정의되어 있다. JDK 문서에 따르면 이 메서드는 Object 인스턴스의 복사본을 반환한다고 명시되어 있다. 중요한 점은 복사된 객체는 기존 참조가 아닌 새로운 객체를 반환한다는 것이다. 또한 new 연산자로 생성한 새로운 객체와의 차이점은 복사된 객체는 원본 객체의 초기 상태가 아닌 현재 상태를 포함한다는 점이다.

클론 메서드 사용법

먼저 CloseAble 인터페이스를 구현해야 한다.

 1 @Override
 2     public Object clone() throws CloneNotSupportedException {
 3         Student s = null;
 4         try {
 5             s = (Student) super.clone();
 6         } catch (CloneNotSupportedException e) {
 7             e.printStackTrace();
 8         }
 9         return s;
10     }

View Code얕은 복제(Shallow Copy):

  1. Student 클래스 정의
 1 public class Student implements  Cloneable {
 2 
 3     private String name;
 4     private int age;
 5     private Professor professor;
 6 
 7     public Student(String name, int age, Professor professor) {
 8         this.name = name;
 9         this.age = age;
10         this.professor = professor;
11     }
12 
13     public String getName() {
14         return name;
15     }
16 
17     public void setName(String name) {
18         this.name = name;
19     }
20 
21     public int getAge() {
22         return age;
23     }
24 
25     public void setAge(int age) {
26         this.age = age;
27     }
28 
29     public Professor getProfessor() {
30         return professor;
31     }
32 
33     public void setProfessor(Professor professor) {
34         this.professor = professor;
35     }
36 
37     @Override
38     public String toString() {
39         return "Student{" +
40                 "name='" + name + '\'' +
41                 ", age=" + age +
42                 ", professor=" + professor +
43                 '}';
44     }
45 
46     @Override
47     public Object clone() throws CloneNotSupportedException {
48         Student s = null;
49         try {
50             s = (Student) super.clone();
51         } catch (CloneNotSupportedException e) {
52             e.printStackTrace();
53         }
54         return s;
55     }
56 }

View Code2. Professor 클래스 정의

 1 public class Professor{
 2     private String name;
 3     private int age;
 4 
 5     public Professor(String name, int age) {
 6         this.name = name;
 7         this.age = age;
 8     }
 9 
10     public String getName() {
11         return name;
12     }
13 
14     public void setName(String name) {
15         this.name = name;
16     }
17 
18     public int getAge() {
19         return age;
20     }
21 
22     public void setAge(int age) {
23         this.age = age;
24     }
25 
26     @Override
27     public String toString() {
28         return "Professor{" +
29                 "name='" + name + '\'' +
30                 ", age=" + age +
31                 '}';
32     }
33 }

View Code3. 실행 코드

 1  @Test
 2     public void cloneT(){
 3         Professor p1 = new Professor("Professor Zhang",30);
 4 
 5         Student s1 = new Student("xiao ming",18,p1);
 6 
 7         System.out.println(s1);
 8 
 9         try {
10             Student s2 = (Student) s1.clone();
11             s2.setName("xiao hong");
12             s2.setAge(17);
13             Professor p2 = s2.getProfessor();
14             p2.setName("Professor Li");
15             p2.setAge(45);
16             s2.setProfessor(p2);
17             System.out.println("복제 후 s1 = " + s1);
18             System.out.println("복제 후 s2 = " + s2);
19         } catch (CloneNotSupportedException e) {
20             e.printStackTrace();
21         }
22 
23     }

View Code4. 실행 결과:

1 Student [name=xiao ming, age=18, professor=Professor [name=Professor Zhang, age=30]]
2 복제 후 s1 = Student [name=xiao ming, age=18, professor=Professor [name=Professor Li, age=45]]
3 복제 후 s2 = Student [name=xiao hong, age=17, professor=Professor [name=Professor Li, age=45]]

문제점: s2의 Professor 정보를 수정했더니 s1도 변경되었다. 하지만 기본형 변수 name과 age는 영향을 받지 않았다. 이는 얕은 복제가 유발한 문제이며, 이를 해결하려면 깊은 복제를 사용해야 한다.

깊은 복제(Deep Copy):

  1. Professor 클래스에 clone 메서드 추가
 1  @Override
 2     public Object clone() throws CloneNotSupportedException {
 3         Professor p = null;
 4         try {
 5             p = (Professor) super.clone();
 6         } catch (CloneNotSupportedException e) {
 7             e.printStackTrace();
 8         }
 9         return p;
10     }

View Code2. Student 클래스의 clone 메서드 수정

 1  @Override
 2     public Object clone() throws CloneNotSupportedException {
 3         Student s = null;
 4         try {
 5             s = (Student) super.clone();
 6             s.professor = (Professor) this.professor.clone();
 7         } catch (CloneNotSupportedException e) {
 8             e.printStackTrace();
 9         }
10         return s;
11     }

View Code참고: 위 해결책에도 한계가 존재한다. 만약 많은 객체들이 복제를 통해 생성된다면 각각의 클래스에 clone() 메서드를 구현하고 깊은 복사를 처리해야 하므로 유지보수성이 떨어진다. 이러한 경우 직렬화를 이용하여 복제 작업을 수행할 수 있다.

 1 public class CloneUtils {
 2     @SuppressWarnings("unchecked")
 3     public static <T extends Serializable> T clone(T obj){
 4         T cloneObj = null;
 5         try {
 6             // 바이트 스트림에 쓰기
 7             ByteArrayOutputStream out = new ByteArrayOutputStream();
 8             ObjectOutputStream obs = new ObjectOutputStream(out);
 9             obs.writeObject(obj);
10             obs.close();
11             
12             // 메모리 할당 후 원본 객체로부터 새 객체 생성
13             ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
14             ObjectInputStream ois = new ObjectInputStream(ios);
15             // 새로 생성된 객체 반환
16             cloneObj = (T) ois.readObject();
17             ois.close();
18         } catch (Exception e) {
19             e.printStackTrace();
20         }
21         return cloneObj;
22     }
23 }

View Code이 유틸리티 클래스를 사용하려면 대상 객체가 Serializable 인터페이스를 구현해야 하며, 그렇지 않으면 복제가 불가능하다.

 1 public class Person implements Serializable{
 2     private static final long serialVersionUID = 2631590509760908280L;
 3 
 4     ..................
 5     // clone() 메서드 제거
 6 
 7 }
 8 
 9 public class Email implements Serializable{
10     private static final long serialVersionUID = 1267293988171991494L;
11     
12     ....................
13 }

View Code따라서 이 유틸리티를 사용할 경우 Serializable 인터페이스만 구현하면 복제가 가능하며, Cloneable 인터페이스를 구현할 필요 없이 직접 clone() 메서드를 작성할 필요가 없다.

  1. 결과 출력
1 Student [name=xiao ming, age=18, professor=Professor [name=Professor Zhang, age=30]]
2 복제 후 s1 = Student [name=xiao ming, age=18, professor=Professor [name=Professor Zhang, age=30]]
3 복제 후 s2 = Student [name=xiao hong, age=17, professor=Professor [name=Professor Li, age=45]]

주의: final 키워드로 선언된 멤버 변수는 깊은 복제가 불가능하다

참고 링크: https://www.cnblogs.com/acode/p/6306887.html

태그: java clone serialization deep copy shallow copy

10월 11일 22:11에 게시됨