clone 메소드와 Cloneable 인터페이스
public class MyClass{
...
}
// 컴파일 에러, clone 메소드를 찾을 수 없다.
myClass.clone();
public class MyClass{
...
public MyClass clone() throws CloneNotSupportedException {
return super.clone();
}
...
}
// 런타임 에러, CloneNotSupportedException 발생
myClass.clone();
public class MyClass implements Cloneable{
...
public MyClass clone() throws CloneNotSupportedException {
return super.clone();
}
...
}
// 정상동작
myClass.clone();
clone 메소드는 해당 객체를 복사하여 새로운 객체를 반환하여 준다. 하지는 clone 메소드는 protected로 선언되어 있어서 하위 클래스에서 public으로 재정의 해주어야 사용할 수 있다. 하지만 하위 클래스에서 public으로 재정의 하여도 clone 메소드를 호출하면 CloneNotSupportedException이 발생한다. 하위 클래스가 Clonable 인터페이스의 구현체이어야 비로소 clone 메소드가 정상적으로 동작한다.
clone 일반 규약
clone 메소드는 어떤 객체 x에 대해 다음 식들을 만족시킨다.
1. x.clone() != x : 객체 x의 복사본은 새로운 메모리 공간에 생성된 복사본이기 때문에 !=를 만족한다.
2. x.clone().getClass() = x.getClass() : 객체 x의 복사본은 객체 x와 동일한 클래스이다.
3. x.clone().equals(x) : 객체 x의 복사본은 객체 x의 필드를 그대로 복사했기 때문에 논리적으로 동일하다.
4. x와 반환된 객체 y는 서로 독립적이어야 한다. 복사 이후 객체 x의 값을 변환하여도 복사본 y의 값을 변하지 않는다.
값복사와 깊은복사
public class MyClass implements Cloneable {
private int[] array;
...
public MyClass clone() throws CloneNotSupportedException {
MyClass myClass = (MyClass) super.clone();
return myClass;
}
...
}
// 복사 후 myClass1의 array 값을 수정하면 myClass2의 array값도 수정된다.
MyClass myClass1 = new MyClass(1);
MyClass myClass2 = myClass1.clone();
값복사란 객체의 필드 값들을 단순히 복사하는 것을 말한다. 기본적인 clone 메소드의 동작 방식이 값복사이다. 단순한 값복사는 의도치 않은 결과를 불러올 수 있다. 위 코드에서 myClass1을 수정하면 그 복사본인 myClass2의 값도 같이 변경된다. MyClass의 array 필드 값은 배열의 메모리 공간을 가르키고 결국 복사본도 본래 객체와 동일한 메모리 공간을 바라보게 되는 것이다.
// 깊은복사1
public MyClass clone() throws CloneNotSupportedException {
MyClass myClass = (MyClass) super.clone();
myClass.array = array.clone();
return myClass;
}
// 깊은복사2
public MyClass clone() throws CloneNotSupportedException {
MyClass myClass = (MyClass) super.clone();
myClass.array = new int[array.length];
for(int i = 0; i < array.length; i++) {
myClass.array[i] = this.array[i];
}
return myClass;
}
이 문제를 해결하기 위해서는 위 코드처럼 메모리 공간을 나타내는 필드의 경우 직접 새로운 메모리 공간을 만들고 그 내용을 복사하는 코드를 넣어주어야 한다. 그리고 이를 깊은 복사라고 한다.
복사생성자와 복사팩토리
// 복사생성자를 이용한 카피
public MyClass(MyClass myClass) {
...
}
// 복사팩토리메소드를 이용한 카피
public static MyClass newInstance(MyClass myClass) {
...
}
clone 메소드는 인터페이스를 구현해야 사용할 수 있고 Exception을 던지는 등 사용하기에 까다롭다. 그리고 참조형필드가 존재할 경우 재정의 하였음에도 깊은 복사를 위한 추가 작업이 필요하다. 때문에 꼭 clone을 사용해야하는 상황이 아니라면 복사 생성자나 복사 팩토리를 정의하여 사용하는것이 사용하기에도 직관적이고 개발하기에도 편리하다.
'STUDY > 이펙티브자바' 카테고리의 다른 글
4-1) 클래스와 멤버의 접근 권한을 최소화하라 (0) | 2022.09.02 |
---|---|
3-5) Comparable을 구현할지 고려하라 (0) | 2022.09.01 |
3-3) toString을 재정의 하라 (0) | 2022.08.31 |
3-2) equals를 재정의 하려면 hashCode도 재정의하라 (0) | 2022.08.31 |
3-1) equals는 일반 규약을 지켜서 재정의하라 (0) | 2022.08.25 |