Java/컬렉션

[Java]ArrayList 자르는 방법

DevStory 2022. 9. 16.

ArrayList 자르는 방법

이번 포스팅은 Java에서 ArrayList를 자를 수 있는 방법을 소개합니다.


ArrayList 클래스의 subList 메서드

ArrayList는 시작 위치(fromIndex)부터 마지막 위치(toIndex)까지 포함된 요소를 반환하는 subList() 메서드를 제공합니다.

public List<E> subList(int fromIndex, int toIndex) {
  subListRangeCheck(fromIndex, toIndex, size);
  return new SubList(this, 0, fromIndex, toIndex);
}

여기서 return문에서 반환되는 타입을 주의 깊게 봐야 하는데, 반환 타입은 List이지만 return문에서 SubList 객체를 반환합니다.

 

SubList 클래스에 대한 설명은 "주의사항 2. ConcurrentModificationException 예외"에서 설명합니다.


예제 1. From부터 To까지 요소를 반환

다음 예제는 ArrayList의 2번째 인덱스부터 5번째 인덱스까지 포함된 요소를 가져옵니다.

public static void main(String args[]) {
  ArrayList<Integer> intArrayList =
          new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6));

  ArrayList<Integer> newArrayList =
          new ArrayList<>(intArrayList.subList(2, 5));

  System.out.println("intArrayList: " + intArrayList);
  System.out.println("newArrayList: " + newArrayList);
}

[실행 결과]

intArrayList: [0, 1, 2, 3, 4, 5, 6]
newArrayList: [2, 3, 4]

 

특정 위치부터 마지막 위치까지 요소를 가져오고 싶은 경우 두 번째 매개변수에 ArrayList의 길이를 전달합니다.

 

다음 예제는 ArrayList의 2번째 인덱스부터 마지막 인덱스까지 포함된 요소를 가져옵니다.

public static void main(String args[]) {
  ArrayList<Integer> intArrayList =
          new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6));

  ArrayList<Integer> newArrayList =
          new ArrayList<>(intArrayList.subList(2, intArrayList.size()));

  System.out.println("intArrayList: " + intArrayList);
  System.out.println("newArrayList: " + newArrayList);
}

[실행 결과]

intArrayList: [0, 1, 2, 3, 4, 5, 6]
newArrayList: [2, 3, 4, 5, 6]

예제 2. From부터 To까지 요소를 제거

기존 ArrayList에서 시작 위치부터 마지막 위치까지 요소를 제거하고 싶은 경우 subList() 메서드 다음에 clear() 메서드를 호출합니다.

public static void main(String args[]) {
  ArrayList<Integer> intArrayList =
          new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6));

  System.out.println("2~5번째 인덱스의 요소 제거하기 전: " + intArrayList);

  intArrayList.subList(2, 5).clear();

  System.out.println("2~5번째 인덱스의 요소 제거하기 후: " + intArrayList);
}

[실행 결과]

2~5번째 인덱스의 요소 제거하기 전: [0, 1, 2, 3, 4, 5, 6]
2~5번째 인덱스의 요소 제거하기 후: [0, 1, 5, 6]

주의사항 1. IndeoxOutOfBoundsException 예외

subList() 메서드의 매개변수로 전달된 시작 위치와 마지막 위치가 잘못된 경우 IndexOutOfBoundsException이 발생합니다.

- 시작 위치가 0보다 작은 경우

- 마지막 위치가 ArrayList의 size() 메서드 반환 값보다 큰 경우

 

다음 예제는 시작 위치가 0보다 작은 경우입니다.

public static void main(String args[]) {
  ArrayList<Integer> intArrayList =
          new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6));

  ArrayList<Integer> newArrayList =
          new ArrayList<>(intArrayList.subList(-2, 5));

  System.out.println("intArrayList: " + intArrayList);
  System.out.println("newArrayList: " + newArrayList);
}

[에러 내용]


주의사항 2. ConcurrentModificationException 예외

다음 소스 코드를 실행하면 ConcurrentModificationException이 발생합니다.

public static void main(String args[]) {
  ArrayList<Integer> intArrayList =
          new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6));

  List<Integer> newList = intArrayList.subList(2, 5);

  System.out.println("[subList() 메서드 호출 후]");
  System.out.println("intArrayList: " + intArrayList);
  System.out.println("newList: " + newList);

  Collections.reverse(newList);

  System.out.println("\n[Collections.reverse(newList) 호출 후]");
  System.out.println("intArrayList: " + intArrayList);
  System.out.println("newList: " + newList);

  intArrayList.removeAll(newList);

  System.out.println("\n[intArrayList.removeAll(newList) 호출 후]");
  System.out.println("intArrayList: " + intArrayList);
  System.out.println("newList: " + newList);
}

[에러 내용]

에러가 발생하기 전에 실행 결과를 살펴봅시다. Collections.reverse() 메서드를 호출하여 newList의 값을 뒤집으면, intArrayList의 2번째 인덱스부터 5번째 인덱스까지 요소가 뒤집어진 것을 확인할 수 있습니다.

 

newList의 값을 뒤집었는데, 왜 intArrayList에 영향이 있는지 이해하려면 ArrayList 클래스의 subList() 메서드가 어떻게 동작하는지 파악해야 합니다.

 

다음 소스 코드는 ArrayList 클래스의 subList() 메서드 구문입니다.

public List<E> subList(int fromIndex, int toIndex) {
  subListRangeCheck(fromIndex, toIndex, size);
  return new SubList(this, 0, fromIndex, toIndex);
}

subList() 메서드가 호출되면 subListRangeCheck() 메서드를 호출하여 매개변수로 전달된 시작 위치와 마지막 위치가 올바른지 체크합니다.

 

시작 위치와 마지막 위치가 올바르지 않으면, IndexOutOfBoundsException 또는 IllegalArgumentException이 발생하고 시작 위치와 마지막 위치가 올바르면 SubList() 생성자 함수를 호출합니다.

 

SubList 생성자 함수의 첫 번째 매개변수로 현재 ArrayList 객체인 this를 전달합니다.

 

참고로 SubList 클래스는 ArrayList 클래스에 내장된 클래스입니다.

 

이제, ArrayList 클래스에 내장된 SubList 클래스의 생성자 함수를 살펴봅시다.

SubList(AbstractList<E> parent,
        int offset, int fromIndex, int toIndex) {
  this.parent = parent;
  this.parentOffset = fromIndex;
  this.offset = offset + fromIndex;
  this.size = toIndex - fromIndex;
  this.modCount = ArrayList.this.modCount;
}

첫 번째 매개변수로 전달받은 현재 ArrayList 객체를 parent라는 필드에 할당됩니다. 즉, subList() 메서드의 반환 값이 할당된 newList가 intArrayList를 참조하게 됩니다.

 

따라서, Collections.reverse() 메서드를 호출하여 newList의 요소를 뒤집으면, 기존 ArrayList 객체인 intArrayList에도 영향이 미칩니다.

 

반대로 기존 ArrayList 객체인 intArrayList에 어떤 작업을 수행하면, newList에도 영향이 미칩니다. intArrayList가 removeAll() 메서드를 호출하면, newList의 parent 필드에 문제가 발생하므로 ConcurrentModificationException이 발생하게 됩니다.

 

ConcurrentModificationException을 방지하고 원본 데이터를 보존하고 싶으면, subList() 메서드의 반환 결과를 새로운 리스트로 생성하도록 다음 소스 코드처럼 생성자 함수를 사용해야 합니다.

ArrayList<Integer> intArrayList =
        new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6));

List<Integer> newList = new ArrayList<>(intArrayList.subList(2, 5));

주의사항 3. IllegalArgumentException 예외

subList() 메서드의 매개변수로 전달된 시작 위치가 마지막 위치보다 큰 경우 IllegalArgumentException이 발생합니다.

 

다음 예제는 subList() 메서드의 매개변수로 전달된 시작 위치가 마지막 위치보다 큰 경우입니다.

public static void main(String args[]) {
  ArrayList<Integer> intArrayList =
          new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6));

  ArrayList<Integer> newArrayList =
          new ArrayList<>(intArrayList.subList(25, 5));

  System.out.println("intArrayList: " + intArrayList);
  System.out.println("newArrayList: " + newArrayList);
}

[에러 내용]

반응형

댓글