Java의 List 사용 시 일부분을 잘라내기 할 경우 sublist()를 사용하는 경우가 있다.
그러나 해당 함수의 리턴 값은 list로 반환되나 여기에 엄청난 함정이 숨어져 있다!
sublist()는 다음과 같이 사용할 수 있다.
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.sublist(0,1);
sublist()를 자세히 들여다 보면 아래와 같은 코드로 되어 있다.
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
위 코드에서 알 수 있듯이 return new SubList 새롭게 메모리 할당하여 list를 반환하고 있으며, 매개변수로 현재 리스트 객체(this)를 넣어 주고 있다. 문제는 새롭게 생성하는 클래스의 생성자이다.
protected SubList(SubList<E> parent, int fromIndex, int toIndex) {
this.root = parent.root;
this.parent = parent;
this.offset = parent.offset + fromIndex;
this.size = toIndex - fromIndex;
위 코드는 SubList 클래스를 생성 시 호출되는 생성자 이다. this로 넘겨줬던 리스트의 객체는 parent로 저장되어 관리되는 것을 확인 할 수 있다. 예를 들어, 1만개의 리스트가 있는 객체를 자르기 할 경우 1만개의 리스트가 캐싱된다는 얘기다.
또 다른 문제는 sublist()로 반환받은 리스트는 언뜻 보이기에 원본 리스트에서 인덱싱되어 보이지만 사실상 그렇지 않다. 아래 코드를 실행해 보자.
List<Integer> alist = new ArrayList<>();
alist.add(1);
alist.add(2);
alist.add(3);
List<Integer> blist = alist.sublist(0,1);
blist.remove(0);
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231)
at java.util.ArrayList$SubList.listIterator(ArrayList.java:1091)
at java.util.AbstractList.listIterator(AbstractList.java:299)
at java.util.ArrayList$SubList.iterator(ArrayList.java:1087)
...
왜 이런 에러가 날까?
정답은 SubList 클래스의 생성자에 숨어져 있다. 생성자에 있었던 parent의 값이 함께 반환되기 때문에 인덱싱 한 값이더라도 영향을 받는다.
생성 방법은 아래와 같다.
List<Integer> subList = new ArrayList<>(alist.subList(0, 3));
여기까지, List의 간편하면서도 무서운 면을 살펴 보았다. 어찌보면 아는 사람만 아는 부분일 수 도 있기 때문에 중요한 부분이라 정리를 해보았다.
누군가 도움이 되었기를...