티스토리 뷰
Trouble Shooting: java list ( Arrays.asList(), List.of() )사용시 UnsupportedOperationException 해결법
junojuno 2022. 12. 19. 17:35자바에서 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
'Programming > Java' 카테고리의 다른 글
Java Exception: Checked vs Unchecked, @SneakyThrows (0) | 2023.02.28 |
---|---|
@SafeVarargs Annotation (추가 학습 필요) (0) | 2022.12.19 |
Junit5 (0) | 2022.12.01 |
Junit5에서 assertIterableEquals과 assertLinesMatch의 차이점 (0) | 2022.10.25 |
컬렉션 프레임워크 (Collections Framework) (0) | 2022.06.11 |
- Total
- Today
- Yesterday