코딩테스트 문제를 푸는 중에 Map을 사용해야 할 때가 있었다. Map의 value 타입으로 List를 사용했는데 이때 value를 초기화할 때 Arrays.asList()를 사용하였다. 하지만 Arrays.asList()로 생성된 List에서 값이 추가되지 않고 UnsupportedOperationException
예외가 발생하는 것이였다...
List.of()는 불변 객체를 만들고 Arrays.asList()는 변경 가능한 리스트를 만들어 주는 것이라고 생각했는데 기존에 알고 있던 게 틀렸던 것!!
그래서 학습을 다짐하게 되었다.
우선 가장 기본이 되는 new ArrayList<>() 부터 알아보자.
List<Integer> list = new ArrayList<>();
new ArrayList<>()는 ArrayList를 생성하는 가장 기본이 되는 방법이다.
return type은 java.util.ArrayList
의 ArrayList이다. return type은 ArrayList지만 보통 다형성을 이용하기 위해 List interface로 선언한 변수를 사용해 이용한다.
List<Integer> list = Arrays.asList();
List는 new ArrayList<>() 말고도 Arrays.asList()로도 생성할 수 있다.
🧐그럼 일반적으로 사용하는 new ArrayList<>()와 어떤 차이가 있을까?
<Arrays.asList()로 생성되는 ArrayList>
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
<new ArrayList<>()로 생성되는 ArrayList>
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
return type이 다르지만 List 타입으로 객체를 받을 수 있는 이유는 둘 다 AbstractList를 구현하고 있기 때문이다.
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>
AbstractList는 List를 구현하고 있다. 즉 다형성을 상위 타입으로 객체를 받는 것이다.
new ArrayList<>()는 add, remove 등 원소 추가, 삭제가 가능하지만 Arrays.asList()는 추가, 삭제가 불가능하다. 만약 추가, 삭제 하려고 한다면 java.lang.UnsupportedOperationException
예외가 발생한다.
실제 Arrays.ArrayList를 살펴보면 add()와 remove()가 구현되어 있지 않은 것을 살펴볼 수 있다.
여기까지 살펴보면 보통 테스트 픽스쳐를 만들 때는 픽스쳐가 외부에서 변경되는 것을 막을 수 있기 때문에 Arrays.asList()로 List를 생성하면 이점이 있어보인다. 하지만...
@Override
public E set(int index, E element) {
E oldValue = a[index];
a[index] = element;
return oldValue;
}
Arrays.ArrayList는 set 메서드를 구현하고 있어 불변 객체를 보장해줄 수는 없다. 그럼 완벽하게 불변객체를 보장하려면 어떻게 해야 할까?
List.of()는 자바9버전부터 생긴 List를 만드는 또 다른 방법이다.
static <E> List<E> of(E e1) {
return new ImmutableCollections.List12<>(e1);
}
List.of()는 ImmutableCollections를 이용해 List를 생성함으로써 생성된 List의 불변성을 보장해준다.
예를 들어 set 메서드로 list의 값을 수정한다면 UnsupportedOperationException
이 발생한다.
ImmutableCollections는 내부의 List12 static 클래스로 List를 생성해서 객체의 불변성을 보장해주는데 List12는 AbstractImmutableList를 상속 받고 있다.
static abstract class AbstractImmutableList<E> extends AbstractImmutableCollection<E>
implements List<E>, RandomAccess {
// all mutating methods throw UnsupportedOperationException
@Override public void add(int index, E element) { throw uoe(); }
@Override public boolean addAll(int index, Collection<? extends E> c) { throw uoe(); }
@Override public E remove(int index) { throw uoe(); }
@Override public void replaceAll(UnaryOperator<E> operator) { throw uoe(); }
@Override public E set(int index, E element) { throw uoe(); }
@Override public void sort(Comparator<? super E> c) { throw uoe(); }
static UnsupportedOperationException uoe() { return new UnsupportedOperationException(); }
AbstractImmutableList는 위 처럼 객체의 상태를 변경하는 작업에 대해 UnsupportedOperationException
예외를 발생시켜 불변을 보장해준다.
따라서 테스트 픽스쳐를 만들때나 Thread safe를 보장하기 위해 불변 객체를 사용해야 하는 상황에서는 new ArrayList<>()나 Arrays.asList()보다 List.of()를 사용하는 것이 적합하다.
모호하게만 알고 있었던 List 생성법에 대해 학습해보는 시간이였다. 평소 별거 아니라고 생각하고 넘어갔던 주제였는데 이런 기본적이지만 실수할 수 있는 것들을 한 번 짚고 넘어가는 습관을 계속해서 길러 나가자!!
yboy