Arrays.asList( )와 List.of( )
JAVA

Arrays.asList( )와 List.of( )

자바 8, 9를 들어서며 배열에도 정적 팩토리 메서드가 추가되었습니다.

그래서 저도 잘 활용하고 있었는데.. 문제가 발생했습니다.

바로 swap( )을 사용하는 도중에서 말이죠.

 

    public static void main(String[] args) {
        List<Integer> testList1 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
        List<Integer> testList2 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
        List<Integer> result = sortList(testList1);
        System.out.println(result);
    }

    static List<Integer> sortList(List<Integer> list) {
    	대충 Collections.swap()을 사용하는 코드...
        return list;
    }

그런데 재밋는점은, List.of( )를 사용하면 swap( )이 정상적으로 작동하지 않았고, Arrays.asList( )를 사용하면 정상적으로 작동되었습니다.

흠.....

대충 List.of()가 불변의 성질을 가지고 있나?라고 생각이 들었지만 확실하지 않으면 뭐다? 레퍼런스를 보는게 제맛!

 

Arrays.asList( )

@SafeVarargs
public static <T> List <T> asList​(T... a)
Returns a fixed-size list backed by the specified array. (Changes to the returned list "write through" to the array.) This method acts as bridge between array-based and collection-based APIs, in combination with Collection.toArray(). The returned list is serializable and implements RandomAccess.This method also provides a convenient way to create a fixed-size list initialized to contain several elements: List <String> stooges = Arrays.asList("Larry", "Moe", "Curly");
Type Parameters : T - the class of the objects in the array
Parameters : a - the array by which the list will be backed
Returns : a list view of the specified array
  • asList( )는 고정된 크기를 가지는 List를 반환한다.
  • 반환된 목록은 직렬화 가능하고, RandomAccess를 구현한다.
  • 여러 요소를 포함하도록 초기화된 고정 크기의 리스트을 만드는 편리한 방법이다.

asList( ) 코드를 살펴보니, ArraysList 인스턴스를 반환하기는 하는데

public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

 

반환하는 ArrayList가 우리가 알던 클래스가 아닌 내부 클래스 ArrayList입니다!

 private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        @java.io.Serial
        private static final long serialVersionUID = -2764017481108945198L;
        @SuppressWarnings("serial") // Conditionally serializable
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

        @Override
        public int size() {
            return a.length;
        }

        @Override
        public Object[] toArray() {
            return Arrays.copyOf(a, a.length, Object[].class);
        }

        @Override
        @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            int size = size();
            if (a.length < size)
                return Arrays.copyOf(this.a, size,
                                     (Class<? extends T[]>) a.getClass());
            System.arraycopy(this.a, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }

        @Override
        public E get(int index) {
            return a[index];
        }

        @Override
        public E set(int index, E element) {
            E oldValue = a[index];
            a[index] = element;
            return oldValue;
        }

        @Override
        public int indexOf(Object o) {
            E[] a = this.a;
            if (o == null) {
                for (int i = 0; i < a.length; i++)
                    if (a[i] == null)
                        return i;
            } else {
                for (int i = 0; i < a.length; i++)
                    if (o.equals(a[i]))
                        return i;
            }
            return -1;
        }

        @Override
        public boolean contains(Object o) {
            return indexOf(o) >= 0;
        }

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

        @Override
        public void forEach(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            for (E e : a) {
                action.accept(e);
            }
        }

        @Override
        public void replaceAll(UnaryOperator<E> operator) {
            Objects.requireNonNull(operator);
            E[] a = this.a;
            for (int i = 0; i < a.length; i++) {
                a[i] = operator.apply(a[i]);
            }
        }

        @Override
        public void sort(Comparator<? super E> c) {
            Arrays.sort(a, c);
        }

        @Override
        public Iterator<E> iterator() {
            return new ArrayItr<>(a);
        }
    }

그리고 이 클래스의 슈퍼클래스인 AbstractList <E>를 확인해보면 다음과 같은 설명이 나옵니다.

AbstractList <E>
To implement a modifiable list, the programmer must additionally override the set(int, E) method (which otherwise throws an UnsupportedOperationException). If the list is variable-size the programmer must additionally override the add(int, E) and remove(int) methods.

그러니까... 얘는 가변 리스트라면 add( )와 remove( )를 꼭 재정의(오버라이드)하라고 되어있습니다.

하지만 내부 클래스인 ArrayList를 확인해보면? 이 두 메서드가 재정의 되어있지 않습니다.

그래서 여기서 반환하는 리스트는 고정된 크기를 가지는 리스트이고, add와 get을 사용하지 못한다는 결론에 이르게 됩니다!

 

    @DisplayName("add()는 오류가 발생")
    @Test
    void 테스트1() {
        List<Integer> testList1 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
        Assertions.assertThrows(UnsupportedOperationException.class, 
                () -> testList1.add(16));
    }

    @DisplayName("remove() 역시 오류 발생")
    @Test
    void 테스트2() {
        List<Integer> testList1 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
        Assertions.assertThrows(UnsupportedOperationException.class, 
                () -> testList1.remove(1));
    }

    @DisplayName("그 외는 가능1")
    @Test
    void 테스트3() {
        List<Integer> testList1 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
        Assertions.assertThrows(UnsupportedOperationException.class, 
                () -> Collections.swap(testList1, 1, 2));
    }

    @DisplayName("그 외는 가능2")
    @Test
    void 테스트4() {
        List<Integer> testList1 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
        Assertions.assertThrows(UnsupportedOperationException.class, 
                () -> testList1.set(1, 7));
    }

테스트 결과

 

List.of( )

static <E> List <E> of( )
Returns an unmodifiable list containing zero elements. See Unmodifiable Lists for details.
Type Parameters : E - the List's element type
Returns : an empty List
Since : 9
  • 수정 불가능한 리스트를 반환한다.

그래서 코드도 직접 봤더니

    static <E> List<E> of(E... elements) {
        switch (elements.length) { // implicit null check of elements
            case 0:
                @SuppressWarnings("unchecked")
                var list = (List<E>) ImmutableCollections.EMPTY_LIST;
                return list;
            case 1:
                return new ImmutableCollections.List12<>(elements[0]);
            case 2:
                return new ImmutableCollections.List12<>(elements[0], elements[1]);
            default:
                return new ImmutableCollections.ListN<>(elements);
        }
    }

불변을 의미하는 ImmutableCollections 클래스를 반환하는 것을 볼 수 있었습니다.

그래서 모든 작업들이 다 안됩니다.

@DisplayName("더하는것도 안되")
@Test
void 테스트1() {
    List<Integer> testList1 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
    Assertions.assertThrows(UnsupportedOperationException.class, 
            () -> testList1.add(16));
}

@DisplayName("빼는것도 안되")
@Test
void 테스트2() {
    List<Integer> testList1 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
    Assertions.assertThrows(UnsupportedOperationException.class, 
            () -> testList1.remove(1));
}

@DisplayName("바꾸는 것도 안되")
@Test
void 테스트3() {
    List<Integer> testList1 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
    Assertions.assertThrows(UnsupportedOperationException.class, 
            () -> Collections.swap(testList1, 1, 2));
}

@DisplayName("수정도 안되")
@Test
void 테스트4() {
    List<Integer> testList1 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
    Assertions.assertThrows(UnsupportedOperationException.class, 
            () -> testList1.set(1, 7));
}

ImmutableCollections이기 때문에 UnsupportedOperationException가 발생하는 것도 당연합니다.

 

결론

기능 Array.asList( ) List.of( )
삽입 : add( ) X X
삭제 : remove ( ) X X
변경 : set( ), replace( ) O X
Null 허용 여부 O X
  • Array.asList( ) : add( )와 remove( )가 재정의 되어 있지 않는 고정된 크기의 리스트를 반환하는 정적 팩토리 메서드다.
  • List.of( ) : 불변 리스트를 반환하는 정적 팩토리 메서드다.

 

'JAVA' 카테고리의 다른 글

객체 생성  (0) 2021.10.29
박싱 & 언박싱  (0) 2021.08.12