[Java] ArrayDeque.clone()과 ArrayList.clone()은 왜 다를까?

국혜경·2025년 4월 23일
2
post-thumbnail

❓ Question

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> ✔️



❗ Covariant Return Type

이 물음에 답하기 위해서는 우리에게 익숙한 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의 하위 타입을 반환 할 수 있게 되었다.



1️⃣ ArrayList.clone()은 왜 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>로 수정했다면, 그동안 하위 버전에서 만들어진 시스템에 줄 수 있는 영향이 굉장히 컸을 것이다.



2️⃣ ArrayDeque.clone()은 왜 ArrayDeque을 반환할까?

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 2ArrayList<E> 클래스 추가
Java 5Covariant Return Type 추가
Java 6ArrayDeque<E> 클래스 추가

Java가 하위 버전 호환을 중요하게 여기는 언어라는 점을 다시 한 번 느끼게 되었다. 컬렉션 메서드들은 모두 같은 동작을 할 것이라고 생각했지만, 생각보다 다른 부분들도 많은 것 같다!

2개의 댓글

comment-user-thumbnail
2025년 4월 23일

Depth 있는 글 잘 읽었습니다.. casting이 안되길래 짜증내면서 반복문만 돌리고 넘겼는데... 다르시네요bb

1개의 답글