ArrayList 구현하기
6 min read
List
인터페이스는 자바 컬렉션 프레임워크의 일부로, 순서가 있는 요소들의 집합을 다루는 데 사용된다.
AbstractList
는 List
인터페이스를 부분적으로 구현하는 추상 클래스이며,
리스트 구현체들이 공통적인 기능을 재사용할 수 있게 해준다.
자바의 ArrayList
클래스는 AbstractList
클래스를 상속하고 List
인터페이스를 구현한다.
AbstractList
클래스가 이미 List
인터페이스를 간접적으로 구현하고 있기 때문에,
ArrayList
가 List
인터페이스를 명시적으로 구현하지 않아도 되지만,
가독성과 명확성을 높이기 위해 해당 클래스가 List
인터페이스를 준수한다는 것을 명시적으로 보여주고 있다.
특징
- 데이터 접근:
ArrayList
는 내부적으로 배열을 사용하기 때문에, 인덱스를 통해 빠르게 요소에 접근할 수 있다. - 동적 배열 구현:
ArrayList
는 요소를 추가하거나 제거할 때 배열의 크기를 동적으로 변경한다. - 컬렉션 프레임워크의 일부:
List
인터페이스의 구현체이다.
간단한 구현법
ArrayList
의 기능은 방대하다. 해당 아티클에서는 동적 배열로서의 ArrayList
에 집중하기 위해
Generic
, Iterator
, modCount
등 편의 및 성능을 위한 메서드들은 배제하고 구현하였다.
Java17
의 ArrayList
를 참고하여 진행했으나, 가독성등의 이유로 일부 수정한 부분이 있다.
List 인터페이스 정의
아래 인터페이스는 실제 List 인터페이스에 정의되어 있는 메서드 중 일부를 발췌한 것이다.
이번에는 Generic
을 적용하지 않기 때문에, String 배열로 가정하고 진행하였다.
클래스 필드 정의
- DEFAULT_CAPACITY
- 배열이 생성 될 때의 기본 용량 (Java8에서부터 17까지는 10)
- MAX_ARRAY_SIZE
- 할당할 수 있는 배열의 최대 크기
- 일부 VM들은 배열에 헤더 단어를 예약하기 때문에 8을 빼준다.
- EMPTY_ELEMENTDATA
- 아무 것도 없는 빈 배열
- DEFAULTCAPACITY_EMPTY_ELEMENTDATA
- 아무 것도 없는 빈 배열이지만
EMPTY_ELEMENTDATA
와는 의미론적 차이 존재 - 해당 아티클에서는 구현하지 않음
- 아무 것도 없는 빈 배열이지만
- elementData
SimpleArrayList
에add()
되는String
을 담는 배열- 실제
ArrayList
에서는Object
배열 사용
- size
- elementData 배열에 담긴 요소의 총 갯수
- capacity는 배열에 요소가 담길 수 있는 전체 용량을 의미하고, size는 요소의 갯수를 의미한다.
생성자 정의
- 파라미터가 없는 생성자
- 기본 용량으로 초기화
- 파라미터가 0보다 작은 경우
- 예외 발생
- 파라미터가 0인 경우
- 아무 것도 없는 빈 배열
- 파라미터가 0보다 큰 경우
- 입력받은 파라미터 만큼의 용량으로 배열 생성
동적 배열 구현
ArrayList
와 배열의 가장 큰 차이점은 용량이 동적으로 변경할 수 있는지 여부이다.
다음 코드는 동적으로 ArrayList
의 용량을 변경하는 메서드이다.
private String[] grow(int minCapacity)
- 주어진 최소 용량 (minCapacity)을 충족 하기 위해 내부 배열의 크기 증가
calculateNewCapacity
메소드를 호출하여 새로운 용량 계산Arrays.copyOf
를 사용하여 기존 배열을 새로운 크기로 복사 후, 새 배열을elementData
에 할당
private int calculateNewCapacity(int minCapacity)
minCapacity
가 음수인 경우,OutOfMemoryError
발생- 현재 배열의 용량(
oldCapacity
)을 기반으로 새로운 용량(newCapacity
) 계산 - 새 용량은 비트 시프트 연산을 통해 현재 용량의 1.5배로 설정
- 새 용량이
minCapacity
보다 작으면,adjustCapacity
메소드 호출 - 그렇지 않으면,
ensureMaxCapacityLimit
메소드 호출
private int adjustCapacity(int minCapacity)
- 만약 내부 배열이 비어있을 시, (
EMPTY_ELEMENTDATA
)DEFAULT_CAPACITY
와minCapacity
중 더 큰 값 반환 - 그렇지 않으면,
ensureMaxCapacityLimit
메소드를 호출하여 최대 용량 제한 확인
- 만약 내부 배열이 비어있을 시, (
private int ensureMaxCapacityLimit(int minCapacity, int newCapacity)
newCapacity
가MAX_ARRAY_SIZE
를 초과하지 않는 경우,newCapacity
반환- 그렇지 않으면,
hugeCapacity
메소드 호출
private int hugeCapacity(int minCapacity)
minCapacity
가MAX_ARRAY_SIZE
를 초과하는 경우,Integer.MAX_VALUE
반환- 그렇지 않으면
MAX_ARRAY_SIZE
반환
add 구현
public boolean add(String value)
- 리스트의 끝에 새로운 요소 추가
- 용량 확인 후 배열이 가득 찼다면,
grow()
메서드 호출 - 마지막 위치에 요소 추가
- size 증가
public void add(int index, String value)
- 지정된
index
에 새로운 요소 삽입 - 인덱스 유효성 검사
- 용량 확인 후 배열이 가득 찼다면,
grow()
메서드 호출 System.arraycopy()
를 이용하여, 지정된index
이후에 요소가 있을 시 그 요소들을 오른쪽으로 한 칸씩 이동하여 새 요소를 위한 공간 확보- 지정된
index
에 요소 추가 - size 증가
- 지정된
remove 구현
public boolean remove(String value)
- 주어진
value
와 일치하는 첫 번째 요소를 리스트에서 제거 IntStream.range(0, size)
를 사용하여 0부터size - 1
까지의 인덱스에 대한 스트림을 생성합니다.filter
와Objects.equals
를 사용하여value
와 일치하는 요소의 인덱스 탐색findFirst
메소드를 사용하여 일치하는 첫 번째 요소의 인덱스 반환. 만약 일치하는 요소가 없을 시-1
반환- 일치하는 요소가 있으면 (
foundIndex != -1
),removeElementAt
메소드를 호출하여 해당 인덱스의 요소 제거 후true
반환 - 일치하는 요소가 없을 시
false
반환
- 주어진
public String remove(int index)
- 지정된
index
에 있는 요소를 리스트에서 제거 후, 제거된 요소 반환 Objects.checkIndex
메소드를 호출하여index
가 유효한 범위 내에 있는 지 검증removeElementAt
메소드를 호출하여index
위치의 요소 제거- 제거된 요소의 값 반환
- 지정된
private void removeElementAt(int index)
- 지정된
index
에 있는 요소를 리스트에서 제거 numMoved
를 계산하여index
이후에 남아있는 요소 수량 파악- 제거된 요소의 공간을 채우기 위해,
System.arraycopy
메소드를 사용하여index
이후의 모든 요소를 한 칸씩 앞으로 이동 size
1 감소 및 마지막 요소null
로 설정하여 참조 제거
- 지정된
get / set 구현
public String get(int index)
- 지정된
index
에 있는 요소 반환 Objects.checkIndex(index, size)
를 통해 주어진index
검증- 유효한
index
의 경우,elementData[index]
를 사용하여 해당 인덱스의 요소 반환
- 지정된
public String set(int index, String value)
- 지정된
index
에 있는 요소를 새로운value
로 설정 후 이전 값 반환 Objects.checkIndex(index, size)
를 통해 주어진index
검증elementData[index]
를 사용하여 이전 값을oldValue
에 저장elementData[index]
에 새로운value
설정- 이전 값을
oldValue
로 반환
- 지정된
그 외 구현
public void clear()
- 메모리 할당을 새로 하지 않기 때문에 초기 메모리 사용량 유지
- 큰 리스트의 경우, 모든 요소를
null
로 설정하는 데O(n)
의 시간 복잡도 소요 - 가비지 컬렉터가 더 이상 참조되지 않는 객체 회수 가능
- 빈 배열을 넣는 방법으로도 구현 가능하나, 작은 리스트에서는 불필요한 메모리 할당 및 가비지 컬렉션 오버헤드 발생 가능
public String toString()
AbstractCollection
에서는iterator()
를 통해 구현- 현재
iterator()
를 구현하지 않아Arrays
의String toString(Object[] a)
을 기반으로 구현