이 내용을 몇 번을 찾아보는지 모르겠다... 내용이 어렵지는 않은데 정리해 놓지를 않아서 매번 헷갈린다. 제대로 공부해 보고 이번을 마지막으로 더는 헷갈리는 일이 없도록 하자. 내용은 자바의 정석 3판을 참고하였습니다.
clone()
clone()은 자신을 복제하여 새로운 인스턴스를 생성하는 일을 한다. 어떤 인스턴스에 대한 작업을 할 때 원본 인스턴스는 보존해 두고 대신 복제한 새로운 인스턴스에 대해 작업을 할 때 유용하다고 한다. 왜냐하면 새로운 작업이 실패하게 될 경우 원본 인스턴스의 상태로 되돌아갈 수 있기 때문이다.
clone()은 모든 클래스들의 조상인 Object 클래스에서 기본적으로 제공하는 메서드다. clone()을 사용하고자 할 때 Cloneable 인터페이스를 구현해야 하기 때문에(혹은 하위 인터페이스) 이 인터페이스에 있는 메서드라고 착각하는 경우가 있는데 정작 Cloneable 인터페이스에는 선언된 메서드가 하나도 없다. 그럼에도 이 인터페이스를 사용하는 이유는 해당 클래스의 복제를 허용한다는 의미로 생각하면 될 것 같다. Cloneable 인터페이스를 구현하지 않고 사용하면 CloneNotSupportedException 예외가 발생한다. 따라서 try-catch 문으로 감싸야 한다.
Object 클래스에 선언된 메서드를 살펴보면 다음과 같다. 접근 제어자가 protected로 설정되어 있기 때문에 보통 자손 클래스가 사용한다고 보면 되고 다른 클래스에서 clone()에 접근하게 하려면 하위 클래스에서 public으로 열거나 메서드로 감싸서 해당 메서드를 호출하게 해야 한다.
protected native Object clone() throws CloneNotSupportedException;
그러면 clone() 메서드를 어떻게 사용해야 할까? 공식 문서를 살펴보면 다음과 같이 나와 있다.
By convention, the returned object should be obtained by calling super.clone. ... Thus, this method performs a "shallow copy" of this object, not a "deep copy" operation.
인스턴스에 대한 인스턴스를 복제할 때는 super.clone()을 호출해야 한다. 위 내용들을 토대로 이처럼 작성하면 된다. 코드는 자바의 정석 책을 참고했다.
class Point implements Cloneable {
...
public Object clone() {
Object obj = null;
try {
obj = super.clone();
} catch(CloneNotSupportedException e) {}
return obj;
}
}
얇은 복사와 깊은 복사
그리고 clone()은 기본적으로 shallow copy, 얇은 복사를 한다. 얇은 복사는 원본과 복제본이 같은 객체를 공유하기 때문에 원본에 대한 변경이 이루어지면 복제본에서도 같이 영향을 받는다. 반대로 깊은 복사는 원본이 참조하고 있는 객체까지 복제하는 것으로 원본과 복제본이 서로 다른 객체를 참조한다. 따라서 원본을 변경하더라도 복제본에서 영향을 받지 않는다. 어떤 객체에 대한 clone()을 할 경우 그림은 다음과 같다. 얇은 복사는 변수에 들어있는 참조값만 복사하는 걸로 이해해도 괜찮을 것 같다.
package ch09;
public class ShallowDeepCopy { // 책 예제 참고, 일부 추가
public static void main(String[] args) {
Circle c1 = new Circle(new Point2(1,1), 2.0);
Circle c2 = c1.shallowCopy();
Circle c3 = c1.deepCopy();
System.out.println("c1:" + c1); // c1:[p=(1, 1), r=2.0]
System.out.println("c2:" + c2); // c2:[p=(1, 1), r=2.0]
System.out.println("c3:" + c3); // c3:[p=(1, 1), r=2.0]
c1.p.x = 9;
c1.p.y = 18;
System.out.println("=== c1 변경 후 ===");
System.out.println("c1:" + c1); // c1:[p=(9, 18), r=2.0]
System.out.println("c2:" + c2); // c2:[p=(9, 18), r=2.0] (복제본도 같이 바뀜!)
System.out.println("c3:" + c3); // c3:[p=(1, 1), r=2.0]
c2.p.x = 10;
c2.p.y = 10;
System.out.println("=== c2 변경 후 ===");
System.out.println("c1:" + c1); // c1:[p=(10, 10), r=2.0] (원본도 같이 바뀜!)
System.out.println("c2:" + c2); // c2:[p=(10, 10), r=2.0]
System.out.println("c3:" + c3); // c3:[p=(1, 1), r=2.0]
System.out.println("========");
Circle[] cs = new Circle[3];
cs[0] = new Circle(new Point2(1,1), 2.0);
cs[1] = new Circle(new Point2(2,2), 2.0);
cs[2] = new Circle(new Point2(3,3), 2.0);
Circle[] n = cs.clone();
// 1. 원본을 변경 -> 복제본도 같이 변경
cs[0].p.x = 5;
cs[0].p.y = 5;
for(int i=0; i<cs.length; i++) { // [p=(5, 5), r=2.0]
System.out.println(cs[i]); // [p=(2, 2), r=2.0]
} // [p=(3, 3), r=2.0]
for(int i=0; i<n.length; i++) { // [p=(5, 5), r=2.0] (복제본도 같이 바뀜!)
System.out.println(n[i]); // [p=(2, 2), r=2.0]
} // [p=(3, 3), r=2.0]
// 2. 복제본을 변경 -> 원본도 같이 변경
n[0].p.x = 10;
n[0].p.y = 10;
for(int i=0; i<cs.length; i++) { // [p=(10, 10), r=2.0] (원본도 같이 바뀜!)
System.out.println(cs[i]); // [p=(2, 2), r=2.0]
} // [p=(3, 3), r=2.0]
for(int i=0; i<n.length; i++) { // [p=(10, 10), r=2.0]
System.out.println(n[i]); // [p=(2, 2), r=2.0]
} // [p=(3, 3), r=2.0]
}
}
class Circle implements Cloneable {
Point2 p;
double r;
Circle(Point2 p, double r) {
this.p = p;
this.r = r;
}
public Circle shallowCopy() {
Object obj = null;
try {
obj = super.clone();
} catch(CloneNotSupportedException e) {}
return (Circle) obj;
}
public Circle deepCopy() {
Object obj = null;
try {
obj = super.clone();
} catch(CloneNotSupportedException e) {}
Circle c = (Circle) obj;
c.p = new Point2(this.p.x, this.p.y);
return c;
}
public String toString() {
return "[p=" + p + ", r=" + r + "]";
}
}
class Point2 {
int x;
int y;
Point2(int x, int y) {
this.x = x;
this.y = y;
}
public String toString() {
return "(" + x + ", " + y + ")";
}
}
배열도 객체이기 때문에 Object 클래스를 상속받아서 clone()을 사용할 수 있다. 그리고 책에서 배열은 Cloneable 인터페이스와 Serializable 인터페이스를 구현하고 있다고 한다. 또 clone()을 public으로 오버라이딩했기 때문에 따로 상속 관계를 만들지 않아도 직접 호출이 가능하다는 점을 언급한다. 배열의 clone()은 내부적으로 새로운 배열을 생성하고 내용을 복사하는 과정을 거친다. 여기서의 배열은 기본형 배열로 Point[]와 같은 사용자 정의 객체배열이 아님을 주의해야 한다.
내부 과정에서처럼 배열을 생성하고 내용을 복사하기 때문에 서로 다른 참조값을 가져 원본의 변경이 복제본에, 복제본의 변경이 원본에 영향을 끼치지 않는다.
import java.util.Arrays;
public class CloneEx2 { // 자바의 정석 3판
public static void main(String[] args) {
int[] arr= {1,2,3,4,5};
int[] copy = arr.clone(); // 배열 복사의 경우 새로운 배열을 생성하고 내용 복사
arr[0] = 9;
System.out.println(Arrays.toString(arr)); // [9, 2, 3, 4, 5]
System.out.println(Arrays.toString(copy)); // [1, 2, 3, 4, 5]
copy[0] = 18;
System.out.println(Arrays.toString(arr)); // [9, 2, 3, 4, 5]
System.out.println(Arrays.toString(copy)); // [18, 2, 3, 4, 5]
}
}
[참고:https://staticclass.tistory.com/77]
01-4. [자바] clone(), 깊은복사, 얕은 복사 - Object 클래스
clone() 클론 하면 복제가 생각나기 마련인데 이 메서드 역시 자신을 복제하여 새로운 인스턴스를 생성한다👍 clone() 메서드를 오버라이딩 하려면 Cloneable을 구현해야한다. Cloneable인터페이스를 구
staticclass.tistory.com
정리
clone()은 자신을 복제하여 인스턴스를 생성하는 일로 Cloneable 인터페이스를 구현해야 한다. Cloneable 인터페이스를 구현하지 않고 사용하면 CloneNotSupportedException 예외가 발생하기 때문에 try-catch로 묶어야 한다. super.clone()으로 사용한다.
얇은 복사 | 깊은 복사 |
clone()이 채택하는 방식, 복사본과 원본이 같은 객체 참조 | 각자 다른 객체 참조 |
'JAVA' 카테고리의 다른 글
[JAVA] comparable, comparator 비교 (0) | 2024.07.03 |
---|---|
[JAVA] System.in.read() (1) | 2024.06.30 |
[JAVA] delete(), deleteCharAt() (0) | 2024.05.07 |
[JAVA] toString(), String.valueOf()의 차이 (0) | 2024.03.20 |
[JAVA] String, Stringbuffer, Stringbuilder 차이 (0) | 2024.03.14 |