Cloneable인터페이스
1)복제해도 되는 클래스임을 명시하는 용도
2)clone메서드가 선언된 곳이 Clonable이 아닌Object객체,protected
3.clone의 동작 방식을 결정한다
1.실무에서 Clonable을 구현한 클래스는 clone메서드를 public으로 제공, 사용자는 당연히 복제가 제대로 이뤄진다고 기대한다.
clone메서드의 일반규약
-->객체의 복사본을 생성해 반환.
-->복사는 그 객체를 구현한 클래스에따라 다를수있지만 일반적인 의도는 다음과같다.
-->어떤객체x에 대해 다음 식은 모두 참이다.
1)x.clone()!=x
2)x.clone().getClass()==x.getClass()
3)x.clone.equals(x)
4)x.clone().getClass()==x.getClass()
2.제대로 동작하는 clone메서드를 가진 상위클래스를 상속해 Cloneable을 구현하고싶다면?
1)super.clone을 호출
-->이렇게 호출한 객체는 완벽한 복제본
-->클래스에 정의된 모든 필드는 원본 필드와 똑같은 값을 갖는다.
해당특징을 고려해 PhoneNumber의 clone메서드를 구현
-->가변상태를 참조하지않는 클래스용 clone메서드
@Override
public PhoneNumber clone(){
try{
return (PhoneNumber)super.clone();
}catch(CloneNotSupportedException e){
throw new AssertionError(); //일어날수없는 일이다.
}
}
해당메서드가 동작하게하려면 Cloneable을 구현해야함
public class Stack{
private Object[] elements;
private int size=0;
private static int DEFAUT_INITAL_CAPACITY=16;
public Stack(){
this.elements= new Object[DEFAUT_INITAL_CAPACITY];
}
public void push(Object e){
ensureCapacity();
elements[size++]=e;
}
public Object pop(){
if(size==0)
throw new EmptyStackException();
Object result= elements[--size];
elements[size]=null; //다쓴 참조 해제
return result;
}
//원소를 위한 공간을 적어도 하나이상 확보한다.
private void ensureCapacity(){
if(elements.length==size)
elements= Arrays.copyOf(elements, 2*size+1);
}
}
clone메서드가 단순히 super.clone의 결과를 그대로 반환한다면
size필드는 올바른 값을 갖지만 , elements필드는 원본 인스턴스와 똑같은 배열을 참조하게됨
-->원본이나 복제본중 하나를 수정하면 다른 하나도 수정되어 불변식을 해치게됨.
3.clone메서드는 사실상 생성자와 같은 효과를 나타냄
-->원본 객체에 아무런 해를 끼치지않은 동시에 객체의 불변식을 보장해야한다.
가변상태를 참조하는 클래스용 clone메서드
@Override
public Stack clone(){
try{
Stack result= (Stack)super.clone();
result.elements= elements. clone();
return result;
}catch(CloneNotSupportedException e){
throw new AssertionError();
}
}
1)배열의 clone은 런타임타입,컴파일타입 모두가 원본과똑같은 배열을 반환
2)배열을 복제할때는 clone메서드를 사용하는것이좋음!
-->단 final필드라면 새로운값을 할당할수밖에없기때문에 앞서 말한 방식이 동작하지않음
-->Clonalbe아키텍챠는 가변객체를 참조하는 필드는 final필드로 선언하라 라는 일반용법과 충돌함.
복제할수있는 클래스를 만들기위해 일부클래스에서 final을 제거해야할때도있음
4.단순한 버킷배열의 메서드보다 각 버킷을 구성하는 연결리스트를 복사하는 것이 좋다
import java.util.Objects;
public class HashTable implements Cloneable {
private static class Entry{
final Objects key;
Object value;
Entry next;
Entry(Objects key, Object value, Entry next) {
this.key = key;
this.value = value;
this.next = next;
}
//이 엔트리가 가리키는 연결리스트를 재귀적으로 복사
Entry deepCopy(){
return Entry(key,value,next==null?null:next.deepCopy());
}
}
@Override
protected HashTable clone() {
try {
HashTable result= (HashTable) super.clone();
result.buckets= new Entry[buckets.length];
for(int i=0; i<buckets.length; i++){
result.buckets[i]= buckets[i].deepCopy();
return result;
}
}catch (CloneNotSupportedException e){
throw new AssertionError();
}
//return super.clone();
}
//나머지 코드는 생략
}
1)private 클래스인 Entry는 깊은 복사를 지원하도록 보강
2.clone메서드는 적절한 크기의 새로운 버킷배열을 할당한 다음 원래 버킷배열을 순회 ->비지않은 각 버킷에 대해 깊은 복사를 수향
3.deepCopy()메서드는 자신이 가리키는 연결리스트 전체를 복사하기위해 자신을 재귀적으로 호출
-->재귀호출때문에 원소수만큼 스택 프레임을 소비하여 리스트가길면 스택오버플로우의 위험이있음
해당 문제를 피하려면 재귀호출 보다 반복순회 방향으로 순회해야함
-->HashTable의 경우 buckets필드를 새로운 배열로 초기화후에
put메서드를 통해 둘의내용을 동일하게 만듬.
6.public인 clone메서드에서는 throw절을 없애야한다.
-->검사 예외를 던지지않아야 그 메서드를 사용하기 편하기 때문
7.Clonable을 구현한 스레드 안전 클래스를 작성할때에는 clone메서드 역시 적절히 동기화 해야함
8.요약하자면 Cloneable을 구현한 모든 클래스는 clone을 재정의 해야한다.
-->이때 접근자는 public으로, 반환타입은 클래스 자신으로 변경
9.복사 생성자와 복사 팩터리라는 더 나은 객체복사 방식을 제공할수있다.
-->복사생성자란?
-->단순히 자신과 같은 클래스의 인스턴스를 인수로 받는 생성자를 의미.
복사생성자
public Yum(Yum yum){,...}
복사팩터리
public static Yum newInstance(Yum yum){...};
복사생성자의 특징
1)언어 모순적이고 위험천만한 객체생성을 사용하지 않는다
2)엉성하게 문서화된 규약에 기대지않는다
3)정상적인 final 필드용법과도 충돌하지않는다
4)불필요한 검사예외를 던지지않고 형변환도 필요치않다.
5)해당클래스가 구현한 인터페이스 타입의 인스턴스를 인수로 받을수있다.
-->Collection이나 Map타입을 받는생성자를 제공
-->이들을 이용면 클라이언트는 원본의 구현타입에 얽매이지않고 복제본의 타입을 직접 선택가능
ex)
1)HashSet객체 s를 TreeSet타입으로 복제가능
2)clone으로 불가능한 이 기능을 생성자로는 간단히 new TreeSet<>(s)로 처리 가능