woniper

AbstractList.add(E)는 쓸 수 없어! 본문

Java

AbstractList.add(E)는 쓸 수 없어!

woniper1 2017. 9. 7. 08:30

개발 중에 list.addAll(T); 와 같은 코드 작성할 일이 있었다. 하지만 계속 java.lang.UnsupportedOperationException 에러가 나는 게 아닌가? 결론부터 말하자면 생각 없이 Collections.emptyList();로 초기화했기 때문이다.

java.lang.UnsupportedOperationException

java 문서에는 이 Exception을 이렇게 설명한다.

  1. 요청한 오퍼레이션이 지원되지 않는 경우 Exception 발생
  2. UnsupportedOperationException는 Java Collection Framework에 Exception 클래스다.

그럼 Java Collection Framework를 사용하는 경우 비정상 요청을 한 경우 throw 되는 것인가??

Collections.emptyList()

아래 코드는 emptyList 메소드다. 단순히 EMPTY_LIST라는 멤버 변수를 반환 하는데, 주석에 immutable한 empty list를 반환 한다고 쓰여있다.

/**
 * Returns an empty list (immutable).  This list is serializable.
 *
 * <p>This example illustrates the type-safe way to obtain an empty list:
 * <pre>
 *     List&lt;String&gt; s = Collections.emptyList();
 * </pre>
 *
 * @implNote
 * Implementations of this method need not create a separate <tt>List</tt>
 * object for each call.   Using this method is likely to have comparable
 * cost to using the like-named field.  (Unlike this method, the field does
 * not provide type safety.)
 *
 * @param <T> type of elements, if there were any, in the list
 * @return an empty immutable list
 *
 * @see #EMPTY_LIST
 * @since 1.5
 */
@SuppressWarnings("unchecked")
public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}

EMPTY_LIST 멤버 변수는 public static final List EMPTY_LIST = new EmptyList<>(); EmptyList 클래스라는 놈으로 초기화하고 있다.

EmptyList

아래 코드가 바로 EmptyList 클래스다. ArrayList와 마찬가지로 AbstractList를 상속하고 있다. add()addAll() 메소드가 없는 것을 확인할 수 있다. 왜냐하면, immutable 한 list니까!

/**
 * @serial include
 */
private static class EmptyList<E>
    extends AbstractList<E>
    implements RandomAccess, Serializable {
    private static final long serialVersionUID = 8842843931221139166L;

    public Iterator<E> iterator() {
        return emptyIterator();
    }
    public ListIterator<E> listIterator() {
        return emptyListIterator();
    }

    public int size() {return 0;}
    public boolean isEmpty() {return true;}

    public boolean contains(Object obj) {return false;}
    public boolean containsAll(Collection<?> c) { return c.isEmpty(); }

    public Object[] toArray() { return new Object[0]; }

    public <T> T[] toArray(T[] a) {
        if (a.length > 0)
            a[0] = null;
        return a;
    }

    public E get(int index) {
        throw new IndexOutOfBoundsException("Index: "+index);
    }

    public boolean equals(Object o) {
        return (o instanceof List) && ((List<?>)o).isEmpty();
    }

    public int hashCode() { return 1; }

    @Override
    public boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        return false;
    }
    @Override
    public void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
    }
    @Override
    public void sort(Comparator<? super E> c) {
    }

    // Override default methods in Collection
    @Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
    }

    @Override
    public Spliterator<E> spliterator() { return Spliterators.emptySpliterator(); }

    // Preserves singleton property
    private Object readResolve() {
        return EMPTY_LIST;
    }
}

하지만 EMPTY_LIST.addAll() 호출이 가능하다.

Collections.emptyList(); 메소드로 생성한 List는 ArrayList가 아니다. AbstractList라는 같은 부모를 바라보고 있지만, 주석에서 설명하듯 EMPTY_LIST는 immutable한 list이기 때문에 add(), addAll() 메소드는 AbstractList에 메소드가 호출된다.

AbstractList.add(), addAll()

addAll() 메소드는 결국 아래 보이는 add(int, E) 메소드를 호출하는데, 여기서 바로 UnsupportedOperationException를 던진다.

/**
 * {@inheritDoc}
 *
 * <p>This implementation always throws an
 * {@code UnsupportedOperationException}.
 *
 * @throws UnsupportedOperationException {@inheritDoc}
 * @throws ClassCastException            {@inheritDoc}
 * @throws NullPointerException          {@inheritDoc}
 * @throws IllegalArgumentException      {@inheritDoc}
 * @throws IndexOutOfBoundsException     {@inheritDoc}
 */
public void add(int index, E element) {
    throw new UnsupportedOperationException();
}

Collections.emptyList() 만 그럴까?

아마 Arrays.asList(T...) 메소드도 많이 사용할 것이다. 이 메소드 또한 add(E) 메소드를 사용할 수 없다.



테스트

public class CollectionTests {

    @Test(expected = UnsupportedOperationException.class)
    public void testArraysAdd() throws Exception {
        Collection<String> list = Arrays.asList("a", "b", "c");
        list.add("d");
        fail();
    }

    @Test(expected = UnsupportedOperationException.class)
    public void testEmptyListAdd() throws Exception {
        Collection<String> list = Collections.EMPTY_LIST;
        list.add("a");
        fail();
    }
}

결론

생각하고 쓰자.

Comments