티스토리 뷰

자바에서 array를 list로 변환하는 방법에는 두 가지 방법이 있다.

 

자바에서 Array를 list로 변환하기 위해서 

1.  Arrays.asList(array) 방법

2. List.of(array) 방법

총 두 가지 방법이 있다.

 

차이점은 무엇일까?

 

1. 변경가능 여부 (Mutable / Immutable)

Arrays.asList()로 반환된 list는 변경이 가능하다.

하지만, List.of()에서 반환된 메서드는 변경이 불가능하다.

 

Arrays.asList 메서드를 뜯어보면 다음과 같이 List<T>를 리턴한다.

@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

 

반면 List.of 메서드를 뜯어보면 다음과 같은 코드를 리턴한다.

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

List.of()메서드는 위에서 볼 수 있듯, ImmutableCollections를 반환하는 것을 볼 수 있다.

그러므로 list.set나 add를 통해서 접근하면 UnsupportedOperationException이 발생한다.

 

개인적인 실험)

List<Integer> asList = Arrays.asList(new Integer(1), new Integer(3));
List<Integer> listOf = List.of(new Integer(1), new Integer(4), new Integer(1), new Integer(1), new Integer(1), new Integer(1), new Integer(1), new Integer(1), new Integer(1), new Integer(1), new Integer(1), new Integer(1), new Integer(1), new Integer(1));
asList.set(0, 3);
System.out.println(asList);
listOf.set(0, 3);
System.out.println(listOf);

결과)

하지만 Arrays.asList 통해서 add를 하니 똑같이 UnsupportedOperationException가 발생했는데, 그 이유는 asList()를 통해 반환하는 ArrayList가 알고 있는 ArrayList와는 다른, Arrays 클래스에 존재하는 내부클래스로 아래와 같이 존재한다.

보면 add 가 없다. 그래서 지원하지 않기 때문에 예외가 발생하는 것이다.

private static class ArrayList<E> extends AbstractList<E>
    implements RandomAccess, java.io.Serializable
{
    private static final long serialVersionUID = -2764017481108945198L;
    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);
    }
}

2. Null 사용 여부

Arrays.asList()는 null값을 허용한다.

하지만 List.of()는 null값을 허용하지 않는다.

 

List.of()가 반환하는 ImmutableCollections의 ListN 내부 클래스를 보면 다음과 같이  코드가 구성되어있다.

@SafeVarargs
ListN(E... input) {
    // copy and check manually to avoid TOCTOU
    @SuppressWarnings("unchecked")
    E[] tmp = (E[])new Object[input.length]; // implicit nullcheck of input
    for (int i = 0; i < input.length; i++) {
        tmp[i] = Objects.requireNonNull(input[i]);
    }
    elements = tmp;
}

 

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

obj가 null이면 NPE를 발생시킨다.

반면 Arrays.asList() 메서드는 디버깅을 해보니 TransformerManager를 통해서 Map을 통해서 null 처리를 해주는 것 같은데, 어떤 방법인지는 정확히는 모르겠지만 null 사용이 가능하다.

 

3. 참조와 비참조

 

Arrays.asList는 위에서 본것 처럼 Arrays 클래스의 내부 ArrayList 클래스를 반환한다.

그 클래스에는 add나 remove 메서드가 존재하지 않아 UnsupportedOperationException가 발생한다.

Arrays.asList(array)는 참조를 사용하기 때문에 배열의 값이 변경되면 list에도 영향이 간다.

위 ArrayList 내부 클래스의 생성자를 보면 array를 null 체크만 해서 E[ ] 배열에 == 로 연결하여 참조를 사용한다.

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

 

하지만 List.of() 메서드는 아래와 같이 새로운 tmp 배열을 만들어 값을 복사해 독립적인 객체를 만들기에 참조가 일어나지 않는다.

@SafeVarargs
ListN(E... input) {
    // copy and check manually to avoid TOCTOU
    @SuppressWarnings("unchecked")
    E[] tmp = (E[])new Object[input.length]; // implicit nullcheck of input
    for (int i = 0; i < input.length; i++) {
        tmp[i] = Objects.requireNonNull(input[i]);
    }
    elements = tmp;
}

4. 메모리 사용

Arrays.asList는 위에서 보듯 참조하기 때문에 heap 메모리에 더 많은 객체를 생성하므로 더 많은 오버헤드 공간을 차지한다.

따라서 요소 변경하지 않고 단순 값만을 표기할 경우 List.of를 사용하는 것이 좋다.

 

5. 그래서 어떻게 UnsupportedOperationException 를 해결할까

 

Arrays.ofList()를에 그러면 add나 remove를 하기 위해서는 어떻게 해야할까?

new ArrayList(Arrays.ofList())로 생성하면 된다.

 

이 또한 코드를 뜯어 보았다.

new ArrayList(Collection) 을 통해 생성자로 collection을 받으면 

public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        // replace with empty array.
        elementData = EMPTY_ELEMENTDATA;
    }
}

위의 코드가 실행된다.

Arrays.copyOf()를 통해서 배열의 참조 복사가 아닌 값 복사가 일어난다.

따라서 Arrays.asList()로 반환된 List가 새로운 List로 값 복사가 되어 Arrays 클래스의 내부 클래스인 ArrayList가 아닌 흔히 알고 있는 ArrayList의 자료구조로 값 복사가 되어 add와 remove등의 메서드를 사용할 수 있게 되는 것이다.

 

6. Map.of()를 통한 Map객체 생성

 

Map.of()를 통해서 List.of() 처럼 map을 생성할 수 있다.

하지만 10개의 개수제한이 있으며 11개 부터는 Map.ofEntries()를 통해서 Map.entry(key, value)를 사용해 생성해야한다.

 

 

Reference : 

https://jaehoney.tistory.com/144

 
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday