카테고리 없음
[JAVA] clone 메소드(깊은복사, 얕은복사)
부에나온다
2024. 3. 8. 10:55
1. clone 메소드
- Object.clone() 메소드는 인스턴스 객체의 복제를 위한 메소드로, 해당 인스턴스를 복제하여 새로운 인스턴스를 생성해 그 참조값을 반환한다.
- clone() 메소드를 사용하기 위해서는 오버라이딩을 해야 되는데, 이때 데이터의 보호를 이유로 Cloneable 인터페이스를 구현한 클래스의 인스턴스만이 사용할 수 있다.
2. 깊은복사 / 얕은복사
- 자바에서 객체와 같은 참조 변수는 직접 값을 저장하는게 아닌 힙(Heap) 영역에 데이터를 저장하고, 그의 주소값을 저장하는 식으로 구성되어 있다.
- 기본형 타입이 아닌, 객체와 같은 참조형 타입의 변수를 그대로 복제한다면 값이 복사되는 것이 아닌 주소값이 복사되어 결국 같은 힙의 의 데이터를 바라보는 꼴이 되어버린다.
- 이런한 복제를 얕은복제(shallow copy) 라고 하며, 얕은복사는 원본을 변경하면 복사본도 영향을 받는다.
- 반면에, 원본에 참조하고 있는 힙의 데이터까지 복제하는것을 깊은복제(deep copy) 라고 하며, 깊은복제는 서로 다른 객체를 참조하기 때문에 원본의 변경에 복사본이 영향을 미치지 않는다.
// 객체 복사 메소드를 사용하기 위해서는 Cloneable 인터페이스를 구현해서 clone을 재정의 해야함
class User implements Cloneable {
private String name;
public void setName(String name) {
this.name = name;
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Main {
public static void main(String[] args) {
try {
// 얕은 복사(shallow copy)
User user = new User();
user.setName("Edward");
User copy = user;
System.out.println(user.hashCode()); // 622488023
System.out.println(copy.hashCode()); // 622488023
System.out.println(user.equals(copy)); // true - 둘이 동인할 힙데이터를 바라보고 있기 때문에
// 깊은 복사(deep copy)
User user2 = new User();
user2.setName("Edward");
User copy2 = (User) user2.clone();
System.out.println(user2.hashCode()); // 1933863327
System.out.println(copy2.hashCode()); // 112810359
System.out.println(user2.equals(copy2)); // false - 둘은 복사되어 생김새만 같지 다른 힙데이터를 바라보고 있기 때문에
} catch(CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
3. 깊은복사 주의사항
- 간단한 단일 클래스 타입정도는 clone()메소드를 사용한 복제가 문제가 없지만, 클래스 타입을 여러개 담고있는 배열을 복제할 때는 주의사항이 따른다.
- 객체 배열은 그 자체로도 참조 타입이며, 가지고 있는 요소의 값 역시 참조 타입이다.
- 이처럼, 참조 타입의 데이터를 가지고 있는 참조 타입의 클래스 자료형을 clone() 했을 경우 어떤 일이 일어날까?
class MyObject{
int id;
String description;
MyObject(int id, String description) {
this.id = id;
this.description = description;
}
}
public class Main {
public static void main(String[] args) {
MyObject[] arrayObj = {
new MyObject(101, "first"),
new MyObject(101, "second"),
new MyObject(101, "third")
};
System.out.println(Arrays.toString(arrayObj)); // [main$1MyObject@251a69d7, main$1MyObject@7344699f, main$1MyObject@6b95977]
MyObject[] arrayObj2; // 복사할 배열
arrayObj2 = arrayObj.clone(); // 배열을 복사해도 내용물 객체의 주소는 똑같다.
System.out.println(Arrays.toString(arrayObj2)); // [main$1MyObject@251a69d7, main$1MyObject@7344699f, main$1MyObject@6b95977]
System.out.println(arrayObj[0].id); // 101
arrayObj2[0].id = 999; // 복사한 arrayObj2의 첫째 객체의 멤버를 변경
// 원본과 복사본의 값이 같다.
System.out.println(arrayObj2[0].id); // 999
System.out.println(arrayObj[0].id); // 999 : arrayObj1 의 첫째 겍체의 멤버도 변경됨
}
}
- 참조 객체인 배열 자체는 완벽히 복제가 되었지만, 배열 내용물 객체는 얕은 복사가 되어버려, 원본과 배열본의 배열 요소가 담고있는 주소값이 같아 바라보고있는 힙의 데이터가 같게 된다.
- 따라서, 이러한 경우는 직접 for문을 돌며 객체 복제를 해주어야한다.