ArrayList는 배열을 사용하는 스택, 큐와 더불어 가장 구현하기 쉬운 자료구조가 아닐까 생각헌다. 배열과 메모리에 대한 이해가 어느정도 되어 있다면 그리 어렵지 않을 것이라 생각하고, 추가적으로 인터페이스와 제네릭, 오버로딩에 대한 지식이 필요하다.

 

 iterator는 구현하지 않았기 때문에 컬렉션에서 제공하는 ArrayList처럼 for each로 순회하거나 iterator()를 호출할 수 없다. 반복자까지 구현하면 포스팅이 너무 커져서 생략했다.

 

 구현하면서 멤버 변수, 메서드의 이름, 파라미터 이름 등은 ArrayList API Documentation를 보고 작성했다. 또한 배열 인덱스 범위를 확인하는데 있어 ArrayList 클래스에서 사용하는 방법이 너무 마음에 들어 나도 적용해보았다. 내가 작성한 코드는 add, remove 등 배열에 접근해야 하는 메서드를 작성할 때 메서드 초기에 계속 범위 체크를 해주고 있었다. 그런데 ArrayList 클래스는 "rangeCheck.." 같은 범위 체크 메서드를 만들어놓고 단 한 줄로 호출하여 검사하니 코드가 눈에 띄게 줄어들었고 중복이 상당히 줄어들어 있었다.

 

 ArrayList를 다루는 메서드들(add, remove, etc)에 대한 구현은 그다지 어렵지 않았다. 하지만 구현하고 난 뒤 ArrayList에서 범위 체크 메서드를 따라하니 거의 같은 코드가 되어 버린 것 같다.

 

 목차 겸 바로가기

 메서드 요약

 멤버 변수

 생성자

 데이터 삽입

 데이터 삭제

 데이터 검색

 데이터 수정

 배열(메모리) 확장 및 축소

 

 

 

 

 아래 "전체 코드 보기"를 누르면 전체 코드를 볼 수 있다.

 

더보기
public class ArrayList<E> {
	private int size;
	private final int DEFAULT_CAPACITY = 10;
	
	private final Object[] EMPTY_ELEMENT_DATA = {};
	private Object[] elementData;
	
	public ArrayList() {
		elementData = EMPTY_ELEMENT_DATA;
	}
	
	public ArrayList(int initialCapacity) {
		if(initialCapacity > 0)
			elementData = new Object[initialCapacity];
		else if(initialCapacity == 0)
			elementData = EMPTY_ELEMENT_DATA;
		else if (initialCapacity < 0)
			throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
	}
	
	private void rangeCheckForAdd(int index) {
		if(index > size || index < 0)
			throw new IndexOutOfBoundsException(
					"Index " + index + " out of bounds for length " + size);
	}
	
	private void rangeCheck(int index) {
		if(index >= size || index < 0)
			throw new IndexOutOfBoundsException(
					"Index " + index + " out of bounds for length " + size);
	}
	
	private void reallocate() {
		if(elementData == EMPTY_ELEMENT_DATA) {
			elementData = new Object[DEFAULT_CAPACITY];
			return;
		}
		
		int capacity = elementData.length + (elementData.length >> 1);
		reallocate(capacity);
	}
	
	private void reallocate(int capacity) {
		Object[] newArray = new Object[capacity];
		
		for(int i = 0; i < size; i++) {
			newArray[i] = elementData[i];
			elementData[i] = null;
		}
		
		elementData = newArray;
	}
	
	public void ensureCapacity(int capacity) {
		if(capacity > elementData.length
				&& capacity >= DEFAULT_CAPACITY)
			reallocate(capacity);
	}
	
	public void trimToSize() {
		if(size < elementData.length) {
			if(size == 0)
				elementData = EMPTY_ELEMENT_DATA;
			else
				reallocate(size);
		}
	}
	
	public boolean add(E value) {
		add(size, value);
		return true;
	}

	public void add(int index, E e) {
		rangeCheckForAdd(index);
		if(elementData.length == size)
			reallocate();
		
		if(index == size) { 
			elementData[size] = e;
		} else {
			for(int i = index; i < size; i++)
				elementData[i+1] = elementData[i];
			elementData[index] = e;
		}

		size++;
	}

	@SuppressWarnings("unchecked")
	public E remove(int index) {
		rangeCheck(index);
		
		E removeElement = (E)elementData[index];
		
		if(index != size-1)
			for(int i = index; i < size-1; i++)
				elementData[i] = elementData[i+1];
		
		elementData[--size] = null;
		return removeElement;
	}

	public boolean remove(Object o) {
		int idx = indexOf(o);
		
		if(idx != -1) {
			for(int k = idx; k < size-1; k++)
				elementData[k] = elementData[k+1];
			elementData[--size] = null;
			return true;
		}
		
		return false;
	}
	
	@SuppressWarnings("unchecked")
	public E get(int index) {
		rangeCheck(index);
		return (E)elementData[index];
	}

	@SuppressWarnings("unchecked")
	public E set(int index, E e) {
		rangeCheck(index);
		
		Object oldElement = elementData[index];
		elementData[index] = e;
		return (E)oldElement;
	}

	public boolean contains(Object o) {
		return indexOf(o) == -1 ? true : false;
	}

	public int indexOf(Object o) {
		for(int i = 0; i < size; i++)
			if(elementData[i].equals(o))
				return i;
		
		return -1;
	}
	
	public int size() {
		return size;
	}

	public boolean isEmpty() {
		return size == 0;
	}

	public void clear() {
		for(int i = 0; i < size; i++)
			elementData[i] = null;
		size = 0;
	}
}

 

 


 

Method Summary (public)

Method Description
boolean add(E)  리스트 끝에 데이터 삽입
void add(int, E)  리스트 index 위치에 전달받은 요소 추가
E remove(int)  리스트 index 위치에 존재하는 요소 삭제하고 삭제된 요소 반환
boolean remove(Object)  리스트에서 특정 요소를 0부터 탐색하여 처음 발견하는 요소를 삭제
E get(int)  리스트 index 위치에 있는 요소를 반환
E set(int, E)  리스트 index 위치에 있는 요소를 수정하고 수정하기 전의 값을 반환
void ensureCapacity(int)
 배열의 크기를 capacity로 축소 혹은 확장
void trimToSize()  배열의 크기를 현재 리스트가 가진 요소의 수에 맞게 축소
boolean contains(Object)  리스트에서 특정 요소에 대한 존재여부를 반환
int indexOf(Object);  리스트에서 특정 요소가 존재하는 index를 반환
int size();  리스트가 가진 요소 수를 반환
boolean isEmpty()  리스트가 비었는지에 대한 여부를 반환
void clear()  리스트를 초기화

 

Method Summary (private)

Method Description
void rangeCheckForAdd(int) 데이터 삽입에 있어서의 index에 대한 유효 범위 확인
void rangeCheck(int) 데이터 삽입을 제외하고 index를 사용하는 상황에서의 유효 범위 확인
reallocate() 배열의 크기를 일정한 배율(1.5)로 확장
reallocate(int) 배열의 크기를 capacity에 따라 축소 혹은 확장

 

 


 

 

Variable

private int size;
private final int DEFAULT_CAPACITY = 10;
	
private final Object[] EMPTY_ELEMENT_DATA = {};
private Object[] elementData;
  • size : 현재 배열에 담긴 요소의 개수를 카운팅하는 변수.
  • DEFAULT_CAPACITY : 사용자가 배열 크기를 지정해주지 않았을 경우의 배열 크기 기본 값.
  • EMPTY_ELEMENT_DATA : 사용자가 배열 크기를 할당하지 않았을 경우 elementData가 가리키게 되는 빈 배열로, length가 0인 것과는 달리 할당되지 않은 배열이라는 의미.
  • elementData : 실제 데이터가 저장될 Object 타입 배열.(모든 클래스는 Object 클래스를 상속받기 때문에 타입이 Object인 배열은 어떠한 데이터도 담을 수 있음을 의미한다.)

 

 

 생성자

public ArrayList() {
	elementData = EMPTY_ELEMENT_DATA;
}
	
public ArrayList(int initialCapacity) {
	if(initialCapacity > 0)
		elementData = new Object[initialCapacity];
	else if(initialCapacity == 0)
		elementData = EMPTY_ELEMENT_DATA;
	else if (initialCapacity < 0)
		throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}

 

 파라미터가 없는 생성자와 정수형 변수 1개를 파라미터로 받는 생성자가 있다. 파라미터가 없는 생성자를 호출할 경우 elementData는 크기가 0인 EMPTY_ELEMENT_DATA를 가리키게 되는데, 이는 아직 메모리가 할당된 적이 없다는 것을 나타낸다. 

 

 파라미터가 1개인 생성자는 파라미터로 배열의 크기를 정할 수 있다. 받는 값이 정수이기 때문에 음수, 0, 정수 등이 입력될 수 있는데 배열을 할당받을 때 음수를 입력할 경우 예외를 발생시키고 0을 입력할 경우 의미없는 코드가 된다. 때문에 0을 입력할 경우 파라미터가 없는 생성자를 호출하였을 때와 같은 동작을 하게 하고 음수를 입력할 경우 인위적으로 예외를 발생시킨다. 파라미터로 1 이상의 값이 들어올 경우 비로서 정상적으로 배열을 할당한다.

 

 

 데이터 삽입

public boolean add(E value) {
	add(size, value);
	return true;
}

public void add(int index, E e) {
	rangeCheckForAdd(index);
	if(elementData.length == size)
		reallocate();
	
	if(index == size) { 
		elementData[size] = e;
	} else {
		for(int i = index; i < size; i++)
			elementData[i+1] = elementData[i];
		elementData[index] = e;
	}
	size++;
}

 

 데이터를 삽입할 때 호출되는 메서드들이다. 데이터만 삽입하는 메서드 add(E)와 특정 위치를 지정하여 데이터를 삽입하는 메서드 add(int, E)가 있다. 데이터만 삽입하는 경우 elementData의 마지막 위치인 index에 삽입되고, 인덱스를 지정하여 삽입하는 경우 해당 위치에 있는 데이터부터 마지막에 있는 데이터까지 모든 데이터를 한칸 씩 뒤로 미루고 해당 위치에 삽입한다.

 

 단 데이터를 삽입하기 전에 index가 정상 범주에 속하는지 rangeCheckForAdd 메서드를 통해 체크한다. 문제가 없다면 배열의 현재 크기와 데이터 수를 비교하여 데이터가 다 찼을 경우 배열의 크기를 재할당한다. 

 

 

 데이터 삭제

@SuppressWarnings("unchecked")
public E remove(int index) {
	rangeCheck(index);
	
	E removeElement = (E)elementData[index];
	
	if(index != size-1)
		for(int i = index; i < size-1; i++)
			elementData[i] = elementData[i+1];
	
	elementData[--size] = null;
	return removeElement;
}

public boolean remove(Object o) {
	int idx = indexOf(o);
	
	if(idx != -1) {
		for(int k = idx; k < size-1; k++)
			elementData[k] = elementData[k+1];
		elementData[--size] = null;
		return true;
	}
	
	return false;
}

 

 데이터를 삭제할 때 호출되는 메서드들이다. 특정 위치에 있는 데이터를 삭제하는 메서드 remove(int)와 특정 데이터를 삭제하는 메서드 remove(Object)가 있다. remove(int)는 먼저 인덱스가 정삼 범주 내인지 확인한 후 정상 범주라면 해당 위치에 있는 데이터를 반환하기 위해 미리 저장해둔다. 삭제 위치가 마지막이 아닐 경우 반복문을 구성하여 삭제할 위치의 데이터를 기준으로 뒤의 데이터를 하나씩 앞으로 당기는 작업을 수행한다. 마지막으로 배열의 마지막 요소에 null을 저장하고 이전에 저장해둔 값을 리턴한다.

 

 remove(Object)는 내부의 indexOf(Object) 메서드를 호출하여 파라미터로 들어온 값이 elementData에 있는지 체크한다. 존재할 경우 0이상의 값을, 존재하지 않을 경우 -1을 리턴한다. -1이 아닐 경우 루프를 구성하여 삭제할 위치의 데이터를 기준으로 뒤의 데이터를 하나씩 앞으로 당기는 작업을 수행하고 elementData의 마지막에 null을 저장한 후 true를 반환한다. 반대로 -1일 경우 false를 반환한다.

 

 

 데이터 검색

@SuppressWarnings("unchecked")
public E get(int index) {
	rangeCheck(index);
	return (E)elementData[index];
}
    
public boolean contains(Object o) {
	return indexOf(o) == -1 ? true : false;
}

public int indexOf(Object o) {
	for(int i = 0; i < size; i++)
		if(elementData[i].equals(o))
			return i;
	
	return -1;
}

 

 주로 특정 데이터를 찾는데 쓰이는 메서드들이다. get(int)는 파라미터로 받은 index로 elementData를 참조하여 데이터를 반환한다.

 

 contains(Object)는 파라미터로 받은 값이 elementData에 존재하는지 탐색하여 존재하는지 존재하지 않는지에 대한 판단만 하여 boolean 값으로 반환한다.

 

  indexOf(Object)는 파라미터로 받은 값이 elementData에 존재하는지 탐색하여 그 위치를 정수형으로 반환한다. 존재한다면 0이상의 값을, 존재하지 않을 경우 -1을 반환한다.

 

 

 데이터 수정

@SuppressWarnings("unchecked")
public E set(int index, E e) {
	rangeCheck(index);
	
	Object oldElement = elementData[index];
	elementData[index] = e;
	return (E)oldElement;
}

 

 데이터를 수정하는 메서드로 특정 인덱스를 지정하여 해당 위치에 있는 값을 수정할 수 있다. 먼저 수정될 위치에 있는 값을 복사한 후 elementData에 있는 값을 수정한다. 마지막으로 처음에 복사해둔 값을 반환한다.

 

 

 내부에서의 배열 관리

private void reallocate() {
	if(elementData == EMPTY_ELEMENT_DATA) {
		elementData = new Object[DEFAULT_CAPACITY];
		return;
	}
	
	int capacity = elementData.length + (elementData.length >> 1);
	reallocate(capacity);
}

private void reallocate(int capacity) {
	Object[] newArray = new Object[capacity];
	
	for(int i = 0; i < size; i++) {
		newArray[i] = elementData[i];
		elementData[i] = null;
	}
	
	elementData = newArray;
}

 

 ArrayList의 핵심은 유동적인 배열의 크기라고 할 수 있다. 파라미터가 없는 메서드가 호출되면 elementData가 할당되어있는 상태인지, 아닌지를 판단한 후 할당되어 있지 않다면 기본값인 10으로 배열을 할당받는다. 이에 해당하지 않는 경우는 현재 배열이 "element.length == size"인 경우 뿐이다. 따라서 배열의 크기를 증가시켜줘야 하는데 이때 새 배열의 크기를 현재 배열 크기에서 1.5배 곱한 크기로 결정하였다. 이 메서드에서는 오로지 생성할 배열의 크기만 결정하고 그 크기를 아래 메서드로 넘겨 실제로 배열 크기를 증설한다.

 

 파라미터가 있는 메서드의 경우 파라미터에 들어온 값으로 배열 크기를 증설한다. 파라미터로 들어온 값으로 배열을 생성하고 기존 배열에 있는 값을 복사한다. 마지막으로 elementData가 가리키는 주소를 새로 만든 배열의 주소로 변경해준다.

 

 

 외부에서의 배열 관리

public void ensureCapacity(int capacity) {
	if(capacity > elementData.length
			&& capacity >= DEFAULT_CAPACITY)
		reallocate(capacity);
}

public void trimToSize() {
	if(size < elementData.length) {
		if(size == 0)
			elementData = EMPTY_ELEMENT_DATA;
		else
			reallocate(size);
	}
}

 

 ensureCapacity 메서드는 파라미터로 들어온 값이 elementData의 크기보다 크면서 DEFAULT_CAPACITY보다 클 경우 파라미터 크기로 배열을 재할당한다. 해당 메서드는 접근제한자가 private인 reallocate와는 달리 public이다. 즉 외부에서 원하는 타이밍에 호출할 수 있는 배열 재할당 메서드라는 뜻이다.

 

 데이터가 계속 삽입되면 증설이 필요하지만 반대로 데이터를 계속 삭제하면 남는 공간이 점점 늘어나게 되는데 이는 메모리 공간 낭비로 이어지게 된다. reallocate가 내부에서 증설을 담당했다면 trimToSize 메서드는 elementData의 크기를 현재 데이터 개수인 size로 알맞게 재할당하는 메서드라고 볼 수 있다. 이 메서드 또한 접근 제한자가 public인 만큼 외부에서 호출가능하다.

 ArrayList는 자바를 공부하면서 가장 많이 사용되는 자료구조 중 하나다. 본 포스팅에서는 ArrayList가 왜 필요한지, 내부적으로 어떻게 동작되는지에 대한 이론을 다룬다. ArrayList는 배열을 기반으로 하기 때문에 먼저 배열에 대한 이해를 필요로 하기 때문에 배열에 대해 간단히 리뷰한 뒤 ArrayList에 대해 알아보고 다음 포스팅인 구현으로 넘어 가겠다.

 

 

 

 배열(Array), 그리고 문제점

 프로그래밍을 공부하다 보면 초기에 배열(Array)이라는 것을 배우게 된다. 변수가 여러 개 필요할 때 일일이 "int a, b, c.. etc"와 같이 선언해야 한다면 접근하기도 관리하기도 쉽지 않다. 변수가 2, 3개라면 모를까 10개 20개, 몇 백 개가 된다사실상 관리가 불가능하기 때문에 같은 성질의 변수가 여러 개일 경우 배열을 사용하게 된다.

 

 변수를 할당받으면 메모리 영역 중 스택에 할당받게 되는데, 변수는 변수명으로만 접근할 수 있기 하기 때문에 변수의 수가 많을 수록 유기적으로 접근하기가 어렵다. 특히 반복문을 통해 같은 성질의 데이터들 탐색하는 것은 엄청난 노가다가 될 것이다. 하지만 배열의 경우 Heap 영역에 메모리를 연속적으로 할당받은 뒤 배열의 시작 주소를 스택에 저장해서 접근할 수 있다. 메모리 상에 데이터가 연속적으로 할당되어 있다는 것은 가장 앞 주소만 기억하고 있다면 다음 데이터가 있는 위치는 배열의 자료형 크기를 더해주는 것으로 주소를 알아낼 수 있다는 것을 의미한다. 이러한 구조는 성격이 비슷한 변수들의 관리를 용이하게 해주고, 간단한 주소 계산을 통해 배열의 어느 요소든 빠른 접근을 가능하게 한다.

 

 하지만 장점이 있으면 단점도 존재한다. 배열은 한 번 할당받으면 크기를 줄이거나 늘일 수 없고, 저장된 데이터가 정렬되어 있다고 할 때 중간의 데이터를 삽입 및 삭제하려고 하면 해당 인덱스를 기준으로 데이터를 밀거나 당기는 작업이 필요해진다. 또한 배열을 초기화할 때 할당받은 메모리를 전부 사용한 상황에서 새로운 데이터를 삽입하려고 하면 공간 부족의 문제가 발생한다. 공간이 부족할 수 있으니 미리 넉넉하게 할당받아 놓는다는 생각을 할 수 있지만 추후 데이터 삽입 여부에 따라 공간 낭비로 이어질 수 있다.

 

 데이터의 개수가 정해져 있고 삭제/삽입이 일어나지 않는 환경이라고 한다면 접근이 빠른 배열이 유리하지만 데이터 삭제/삽입이 빈번하고 데이터의 개수가 유동적인 상황이라면 배열은 결코 좋은 해결 방법이 될 수 없다.

 

 그렇다면 빠른 접근이라는 장점을 유지한 채로 정적인 배열을 동적으로 사용할 수 없을까? 

 

 


 

 

 ArrayList

 위에서 배열의 개념과 장단점을 살펴봤다. 배열은 데이터 삽입/삭제가 잦고 데이터 개수가 유동적인 환경에 적합하지 않다.

 

 ArrayList를 한마디로 표현하면 "사이즈 변경이 가능한 배열"이라고 할 수 있다. ArrayList는 배열의 요소에 접근할 때 대괄호([n])를 사용하지 않고 메서드를 통해서 보다 직관적인 데이터 삽입/삭제/변경을 할 수 있다. 또한 배열에 있는 데이터를 초기화하거나 배열 내 데이터 검색 등 사용자에게 편리한 메서드를 제공한다. 이러한 메서드들의 존재는 사용자가 직접 작성하면서 발생할 수 있는 자잘한 오류를 방지해주는 효과도 있다. 하지만 처음에 언급했듯이 가장 큰 장점은 내부적으로 배열을 사용함에도 불구하고 데이터 개수 제한에 구애받지 않고 얼마든지 데이터를 삽입할 수 있다는 점이다.(여기서 메모리 크기의 한계는 생각하지 않는다.) ArrayList는 이름에서 유추할 수 있듯 내부를 들여다보면 배열을 사용하고 있다. 내부적으로 배열을 사용함에도 불구하고 어떻게 데이터를 무한히 삽입할 수 있는 것일까?

 

 아래 소스코드는 실제 ArrayList 클래스의 일부분을 가져온 것인데 Object형 배열 elementData가 실제로 우리가 add를 호출해서 데이터를 삽입하면 데이터가 저장된다. 소스를 이해하라는 것이 아닌 내부에 배열을 쓰고 있다는 것만을 보고 가면 되겠다.

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    @java.io.Serial
    private static final long serialVersionUID = 8683452581122892189L;

    private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;
    ...
}

 

 


 

 

 ArrayList에서 배열 크기 문제를 해결하는 방법 

final int DEFAULT_CAPACITY = 5;
int[] array = new int[DEFAULT_CAPACITY];

 크기 5의 배열 array를 선언하였다. 만약 위 배열에 데이터가 5개 삽입되었다고 가정한다면 배열의 최대 크기만큼 데이터가 삽입되었기 때문에 더 이상 데이터가 삽입될 수 없는 상태가 된다. 일반적인 배열이라면 여기서 기존 데이터를 유지한 채 새로운 데이터를 삽입하는 방법은 없다.

 

 

 

Memory copy

 ArrayList의 경우를 보자. ArrayList는 초기에 일정한 크기의 배열을 할당받아 놓는다. 사용자가 초기 배열의 크기를 지정해 줄 수도 있지만 지정해주지 않아도 기본값으로 배열이 생성된다. 예를 들어 기본값이 5라고 하면 크기 5의 배열이 생성될 것이다. 그리고 그 배열에 5개의 데이터가 삽입되었다고 하면 더 이상 데이터를 추가할 수 없는 상태가 될 것이다. 이것은 배열의 본질이기 때문에 변하지 않는다. 하지만 ArrayList는 가지고 있는 배열에 데이터를 한계까지 삽입한 경우 더 큰 크기의 새로운 배열을 만드는 것으로 문제점을 극복했다. 현재 배열의 크기보다 더 큰 배열을 만들어 현재 데이터를 모두 이동시킨 후 이제부터 새로 만든 배열을 사용하는 것이다.(이전 배열은 Unreachable Object가 되어 언젠가 가비지 콜렉터에게 수거당한다.) 소스코드로 보면 아래와 같다.

 

 

Object[] newArray = new Object[array.length * 2];
		
for(int i = 0; i < array.length; i++) {
	newArray[i] = array[i];
	array[i] = null;
}
		
array = newArray;

 새로운 배열을 생성하고 기존 데이터를 새로운 배열로 이동시키는 코드다. 데이터를 옮기는 작업이 끝나면 멤버 변수로 가지고 있던 메인 배열의 주소를 새로운 배열로 바꿔준다.

 

 

 


 

 

 

 ArrayList 삽입, 삭제

 그 외에 데이터 삽입, 삭제, 수정 등의 메서드도 지원한다. 배열을 사용하기 때문에 삽입, 삭제는 우리가 흔히 알고 있는 데이터를 뒤로 밀거나 앞으로 당기는 것으로 이루어진다. 메서드의 종류가 너무 많기에 간단히 삽입, 삭제에 대한 메서드만 소개하겠다.

 

 

 add(Object value)

 명실상부 ArrayList에서 가장 많이 사용되는 메서드로, 데이터를 삽입할 때 호출하며 파라미터로 전달한 값을 배열의 가장 마지막 위치에 삽입한다.

 

 


 

 

 add(int index, Object value)

 데이터를 삽입할 때 배열의 끝이 아니라 특정 위치에 데이터를 삽입해야 할 때가 있다. 만약 "ArrayList.add(2, 12)"라는 코드를 호출하게 되면 배열의 2번째 index에 12라는 값을 삽입하라는 뜻인데 해당 메서드가 호출될 경우 index 위치로부터 배열의 끝까지 데이터를 모두 뒤로 한칸씩 미루고 파라미터로 받은 값을 index 위치에 삽입한다.

 

 


 

 

 remove(int index)

 remove(Object value)

 데이터를 삭제하는 방법은 크게 두 가지가 있다. 먼저 첫 번째 방법은 값으로 삭제할 데이터를 찾는 방법이다. remove 메서드의 파라미터로 Object를 넘긴 뒤 ArrayList가 가진 배열의 첫 번째 요소부터 마지막 데이터까지 비교해가며 해당 파라미터와 일치하는 값을 찾는다. 일치하는 값이 있을 경우 해당 위치를 기준으로 뒤에 있는 데이터를 한 칸씩 앞으로 당긴다. 

 

 두 번째 방법은 파라미터로 삭제할 데이터의 위치를 특정한 index를 받는 것이다. 삭제할 위치를 알고 있다면 배열의 장점인 index에 의한 접근으로 빠르게 데이터를 삭제할 수 있다. 값으로 데이터를 찾는 방법에 비해 탐색 시간은 없지만 뒤에 있는 값들을 하나씩 앞으로 당긴다는 점은 동일하기 때문에 어느 정도 부담이 가는 작업이다. 

 

 또한 값으로 데이터를 삭제하려할 때 배열에 해당하는 값이 없다면 null을 반환하지만 인덱스로 접근하는 경우 배열의 크기와 벗어난 경우 예외(Exception)이 발생하니 주의가 필요하다.

 

 

 

 

 


 

 

 

 이 외에 데이터 수정(set), 배열 비우기(clear), 데이터 찾기 등 여러 기능을 지원하지만 여기서는 다루지 않고 다음 포스팅인 구현부에서 소스 코드와 함께 설명하겠다.

 

 

 ArrayList

 

[자바/자료구조] ArrayList 직접 구현하기

 본 포스팅에서는 ArrayList에 대한 보다 깊은 이해와 코딩 능력 상승을 위해 ArrayList를 구현합니다. ArrayList가 내부적으로 어떻게 동작하는지 모르신다면 코드를 보기 전에 ArrayList에 대한 이론을

ku-develog.tistory.com

 

+ Recent posts