clone()
메서드를 사용하다가 생각지 못한 곳에서 IDE의 빨간줄을 만났다. 경고를 확인해보니 ArrayList<E>.clone()
은 Object
를 반환하므로 캐스팅이 필요하다는 내용이었다. 반면, ArrayDeque<E>.clone()
은 ArrayDeque<E>
을 반환하고 있었다.
분명 두 클래스 모두 Object나 Collections의 clone()
을 구현하고 있을 것이다. 그런데 왜 이런 차이가 있는걸까?
두 자료구조의 클래스 상속 관계는 아래와 같다.
java.lang.Object
└── java.util.AbstractCollection<E>
├── java.util.AbstractQueue<E>
│ └── java.util.ArrayDeque<E> ✔️
└── java.util.AbstractList<E>
└── java.util.ArrayList<E> ✔️
이 물음에 답하기 위해서는 우리에게 익숙한 Modern Java 이전으로 거슬러 올라가야 한다.
Java 5부터 도입된 기능 중 Covariant Return Type (공변 타입 변환)이 있다.
이 기능 덕분에 부모 클래스의 메서드를 오버라이딩할 때, 더 구체적인 반환 타입 리턴이 가능해졌다.
예를 들어 Object.clone()
을 살펴보면, 아래와 같이 정의되어 있다.
public class Object{
@IntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
}
Object.clone()
의 반환형은 Object
이다.
따라서, Java 5 이전에는 clone()
을 오버라이딩할 때도 Object
그대로 반환해야 했지만,
Java 5 이후부터는 Covariant Return Type 기능을 활용하여 Object
의 하위 타입을 반환 할 수 있게 되었다.
ArrayList<E>
는 Java 2에서 등장한 아주 오래된 클래스이다.
Covariant Return Type 기능이 등장한 Java 5보다 훨씬 이전에 만들어졌기 때문에, Object.clone()
을 오버라이딩 한 ArrayList<E>.clone()
은 Object
를 리턴할 수 밖에 없었다.
그래서 지금도 ArrayList<E>.clone()
은 아래와 같이 생겼다.
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
}
Java는 하위 호환성을 굉장히 중요하게 생각하는 언어이다.
그래서 나중에 생긴 기능이라 해도 기존 클래스에는 함부로 적용하지 않는다. 만약 Java 5 시점에 이를 ArrayList<E>
로 수정했다면, 그동안 하위 버전에서 만들어진 시스템에 줄 수 있는 영향이 굉장히 컸을 것이다.
ArrayDeque<E>
클래스의 추가 시점은 Java 6이다.
따라서, Java 5에서 등장한 Covariant Return Type 기능을 활용 할 수 있었다.
구현을 살펴보면 아래와 같다.
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable {
public ArrayDeque<E> clone() {
try {
@SuppressWarnings("unchecked")
ArrayDeque<E> result = (ArrayDeque<E>) super.clone();
result.elements = Arrays.copyOf(elements, elements.length);
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
결론적으로 Java 5 이후에 추가된 콜렉션들의 clone()
은 자기 자신의 타입을 반환하므로, 개발자들이 편리하게 사용할 수 있게 되었다.
시기 | 기능 |
---|---|
Java 2 | ArrayList<E> 클래스 추가 |
Java 5 | Covariant Return Type 추가 |
Java 6 | ArrayDeque<E> 클래스 추가 |
Depth 있는 글 잘 읽었습니다.. casting이 안되길래 짜증내면서 반복문만 돌리고 넘겼는데... 다르시네요bb