본문 바로가기
TIL

[Java] List.sublist의 사용법과 주의할 점

by 긍고 2021. 5. 31.
반응형

얼마 전 Batch 로직을 짜면서 길이가 정해져 있지 않은 리스트를 1000개씩 잘라서 특정 메소드를 실행할 일이 있었다. 이런 경우 java.util.ListsubList() 메소드를 사용하여 손쉽게 리스트를 잘라낼 수 있다.

subList 사용법

subList() 메소드는 간단히 두 개의 파라미터만으로 사용할 수 있다. 첫 번째로 잘라낼 배열의 첫 인덱스, 두 번째로 잘라낼 배열의 마지막 인덱스 + 1 값을 넘겨주면 원하는 부분 리스트만을 얻어낼 수 있다. 여기서 주의할 점은 첫 번째 파라미터는 포함이고, 두 번째 파라미터는 포함하지 않는다는 점이다. 코드로 예시를 들면 아래와 같다.

List<Integer> list = Lists.newArrayList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
System.out.println(list.subList(0, 5); // 0 ~ 4 인덱스까지만 

// print: 0, 1, 2, 3, 4

subList 메소드 사용 시 주의할 점

이렇게 subList 메소드를 사용하면 편하게 부분 리스트를 구할 수 있지만 이 메소드를 잘못 사용하면 메모리 누수가 발생할 위험이 있다. 이 문제는 subList 메소드를 깊게 들어가서 살펴보면 그 이유를 알 수 있다.

위 사진은 ArrayList의 subList의 구현 코드이다. 입력된 인덱스가 전체 리스트의 길이 범위 내에 있는지를 subListRangeCheck 메소드를 통해 확인한 뒤에 SubList 객체의 인스턴스를 생성해서 넘겨준다.

여기서 사용된 SubList 객체의 구현 부분을 살펴보면 특이한 부분을 찾을 수 있는데 SubList 객체는 parent List를 Attribute로 갖는다는 점이다. 이러한 SubList 객체의 특징으로 인해 다음과 같은 문제점들이 발생할 수 있다.

발생할 수 있는 문제점

  1. parent 리스트가 변경되는 것이 subList에 영향을 준다.
List<Integer> list = Lists.newArrayList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
List<Integer> subList = list.subList(0, 5);
System.out.println(subList); // 정상
list.remove(0);
System.out.println(subList); // ConcurrentModificationException 발생

// print: 0, 1, 2, 3, 4  
// Exception in thread "main" java.util.ConcurrentModificationException  
// ...

어떤 부모 리스트에 대해 subList를 구한 뒤 부모 List에서 하나 이상의 원소를 삭제한다면 subList가 갖고 있는 parent의 값과 실제 parent 리스트의 값이 일치하지 않아 ConcurrentModificationException이 발생하게 된다.

  1. 만약 subList를 캐시에 저장할 시, 의도치 않은 값들이 캐시될 수 있다.

1000개의 원소를 갖는 부모 리스트에서 5개만을 뽑아 일부만을 캐시에 저장하는 코드를 짤 때 subList를 사용하는 경우가 있을 수 있다. 하지만 이 경우 5개의 원소만 캐싱되는 것이 아니라 SubList 객체 내에 부모 리스트를 저장하고 있으므로 전체 1000개의 원소가 캐싱될 수 있다.

안전하게 사용하기

결국 위에서 설명한 방법대로 SubList를 사용한다면 부모 리스트를 수정할 수 없게 되므로 코드 유연성이 떨어지게 된다. 따라서 SubList를 사용할 때에는 SubList 메소드를 호출해서 얻은 값을 새로 선언한 List에 저장함으로써 부모 리스트와 독립적으로 사용한다면 위에서 언급한 문제 없이 안전하게 사용할 수 있다.

List list = Lists.newArrayList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);  
List subList = new ArrayList<>(list.subList(0, 5));  
System.out.println(subList);  
list.remove(0);  
System.out.println(subList);

// print: 0, 1, 2, 3, 4  
// print: 0, 1, 2, 3, 4  

댓글