Arrays.asList(배열)는 String[], int[] 등의 배열을 List로 바꿀 때 자주 사용하던 메소드였다.
평소에는 배열로만 바꾸고, 데이터를 추가하지 않아서 몰랐는데 이번에 데이터를 추가하다가 아래와 같은 에러가 발생했다.
java.lang.UnsupportedOperationException: null
at java.util.AbstractList.add(AbstractList.java:148) ~[na:1.8.0_201]
at java.util.AbstractList.add(AbstractList.java:108) ~[na:1.8.0_201]
at com.study.jaewonstudy.webservice.web.java.errortest.controller.ErrorTestRestController.list(ErrorTestRestController.java:23) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_201]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_201]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_201]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_201]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197) ~[spring-web-5.3.5.jar:5.3.5]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141) ~[spring-web-5.3.5.jar:5.3.5]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.3.5.jar:5.3.5]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894) ~[spring-webmvc-5.3.5.jar:5.3.5]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.5.jar:5.3.5]
1. 에러 발생 상황
- Arrays.asList() 메소드를 통해 변환한 List에 데이터를 추가시 에러 발생.
- 코드
public Object list() throws Exception {
String[] strArr = {"김치", "바나나", "휴일", "데이트", "커피", "감자", "나이", "수학", "개발"};
List<String> strList = Arrays.asList(strArr);
// 에러 발생(java.lang.UnsupportedOperationException)
strList.add("테스트");
return strList;
}
2. 원인
- Arrays.asList() method 의 결과물은 평소에 사용하는 java.util.ArrayList 가 아니라 Arrays 안에 있는 inner class.
- 평소 사용하는 ArrayList Class
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8683452581122892189L;
...
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
...
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
...
}
- 에러발생시 사용한 Arrays$arrayList Class
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 a.clone();
}
@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) != -1;
}
@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);
}
}
- AbstractList Class
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
protected AbstractList() {
}
...
// 해당 메소드가 실행되는 것이다.
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
...
}
- 위 Arrays Class(Arrays$arrayList Class)를 살펴보면, abstractList를 상속받아 생성된 것을 확인할 수 있다.
- abstractList class에 있는 add 나 addAll 메소드를 override를 하지 않고 있다.
- 그렇기 때문에 add() 메소드를 사용하면 AbstractList Class 에 있는 add() 메소드를 수행하게 된다.
- AbstractList Class 를 살펴보면 add() 메소드 사용시 UnsupportedOperationException을 던지도록 되어있다.
3. 에러 발생시 해결법
- Arrays.asList(배열) 의 결과인 arrayList 는 기존의 java.util.ArrayList 와는 다르며, add() 메소드를 쓸 수 없다.
- ArrayList 로 리스트 선언
- 해당 List에 addAll()메소드를 사용하여, Arrays.asList로 만든 리스트 저장
- add() 메소드 사용
public Object list() throws Exception {
// 1. 에러 발생(java.lang.UnsupportedOperationException)
// String[] strArr = {"김치", "바나나", "휴일", "데이트", "커피", "감자", "나이", "수학", "개발"};
//
// List<String> strList = Arrays.asList(strArr);
//
// strList.add("테스트");
// 2. 정상 동작
String[] strArr2 = {"김치", "바나나", "휴일", "데이트", "커피", "감자", "나이", "수학", "개발"};
List<String> strList2 = new ArrayList<String>();
strList2.addAll(Arrays.asList(strArr2));
strList2.add("테스트");
// [김치, 바나나, 휴일, 데이트, 커피, 감자, 나이, 수학, 개발, 테스트]
System.out.println(strList2);
return strList2;
}
참고
- https://blog.gangnamunni.com/post/Arrays-arrayList-ArrayList/