(Effective Java 2/E) 113. Item 11 - Override clone judiciously

Override clone judiciously

  • 2판 제목 : clone을 재정의할 때는 신중하라
  • 3판 제목 : clone 재정의는 주의해서 진행하라

Cloneable 인터페이스는 어떤 객체가 clone함수를 통해 복제가능하다는 것을 알리기 위한 인터페이스이다.

1
2
public interface Cloneable {
}

실제로 구현을 강제하는 메서드도 하나도 없으며, 단지 Object 클래스의 protected 메서드인 clone 함수의 동작 방식을 결정한다.

Cloneable을 구현한 클래스의 객체에서 clone을 호출하면 해당 객체의 필드값들을 하나하나 복사한 객체를 반환하고

구현하지않은 객체에서 호출하면 CloneNotSupportException 예외를 발생시킨다.

일반적인 인터페이스와 달리 Cloneable의 경우 상위 클래스에 정의된 동작 방식을 변경한 것이므로 이러한 구조를 따라하는 것은 권장되지않는다.

참고 불변 객체는 굳이 clone을 제공하지 않는 것이 좋다.

1. clone 함수의 규약

clone의 규약은 상대적으로 느슨하다.

객체의 복사본을 반환하는 함수이니만큼 클래스마다 상이할 “복사”의 의미만 잘 정의해서 충족하면 된다.

x.clone() != x

위의 조건은 반드사 참이어야 한다.

x.clone().getClass() == x.getClass()

위의 조건은 참이긴 하겠지만 반드시 참이어야하는 것은 아니다.

x.clone().equals(x)

위의 조건은 참이긴 하겠지만 반드시 참이어야하는 것은 아니다.

객체를 복사하는 경우 보통 같은 클래스의 새로운 객체가 만들어지며, 어떠한 생성자도 호출되지 않는다.

2. 예제

다시 PhoneNumber 클래스를 가져와보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public final class PhoneNumber implements Cloneable {
private final short areaCode, prefix, lineNum;
private int hashCode;

@Override
public PhoneNumber clone() {
try {
// 형변환
return (PhoneNumber) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}

try-catch 블록이 필요한 이유는 상술한 CloneNotSupportedException 예외를 던지고 있기 때문이다. 물론 Cloneable을 구현하였으므로 발생하진 않을 것이다.

1
2
3
public class Object {
@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;

이번엔 Stack 예제를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_CAPACITY = 16;

public Stack() {
elements = new Object[DEFAULT_CAPACITY];
}

public void push(Object e) {
ensureCapacity();
elements[size++] = 0;
}

public Object pop() {
if(size == 0){
throw new EmptyStackException();
}
return elements[--size];
}

// 원소를 위한 공간을 적어도 하나 이상 여유를 두며, 늘려야하는 경우 두배 이상 늘린다.
private void ensureCapacity() {
if(elements.length == size) {
elements = Arrays.copyOf(elements, 2*size+1);
}
}
}

Stack 클래스는 가변적인 객체를 참조하고 있으므로 아래와 같이 clone을 재정의한다.

1
2
3
4
5
6
7
8
9
10
@Override
public Stack clone() {
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone();
return result;
} catch (CloneNotSupportedException e){
throw new AssertionError();
}
}

원본 객체를 유지하면서 복제한 객체의 불변을 유지하기 위해 elements배열의 clone을 호출한 것을 확인할 수 있다.