배열은 분명 편리한데, 한 가지 불편한 점이 있다. 크기를 처음에 정해야 한다는 것이다. 10칸짜리 배열을 만들었는데 11번째 데이터가 들어오면? 방법이 없다. 이 문제를 해결하기 위해 ArrayList가 등장했다.
ArrayList는 크기가 자동으로 늘어나는 배열이다. 내부적으로는 배열을 사용하지만, 데이터가 꽉 차면 더 큰 배열을 새로 만들어 자동으로 확장한다. 겉에서 보면 그냥 "크기 제한 없는 배열"처럼 동작한다.
import java.util.ArrayList;
ArrayList<String> list = new ArrayList<>();
list.add("사과");
list.add("바나나");
list.add("딸기");
System.out.println(list); // [사과, 바나나, 딸기]
<String> 처럼 꺾쇠 안에 타입을 명시하는 것이 제네릭(Generics)이다. 어떤 타입의 데이터를 담을지 미리 지정해두면, 꺼낼 때 형변환 없이 바로 쓸 수 있다.

| 구분 | 배열 | ArrayList |
|---|---|---|
| 크기 | 고정 | 자동 확장 |
| 타입 | 기본형 포함 가능 | 객체만 가능 |
| 길이 확인 | length | size() |
| 추가/삭제 | 직접 처리 | 메서드 제공 |
배열은 int, double 같은 기본형을 바로 담을 수 있지만, ArrayList는 객체만 담을 수 있다. 그래서 int 대신 Integer, double 대신 Double처럼 래퍼 클래스(Wrapper Class)를 사용한다.
ArrayList<String> list = new ArrayList<>();
// 추가
list.add("사과"); // 맨 뒤에 추가
list.add(0, "포도"); // 특정 인덱스에 추가
// 조회
String fruit = list.get(0); // 인덱스로 조회
int size = list.size(); // 크기
// 수정
list.set(1, "망고"); // 특정 인덱스 값 교체
// 삭제
list.remove(0); // 인덱스로 삭제
list.remove("바나나"); // 값으로 삭제
// 검색
boolean has = list.contains("딸기"); // 포함 여부
int idx = list.indexOf("딸기"); // 위치
// 전체 삭제
list.clear();
ArrayList는 for-each 문으로 순회하는 게 일반적이다.
ArrayList<String> list = new ArrayList<>();
list.add("사과");
list.add("바나나");
list.add("딸기");
for (String fruit : list) {
System.out.println(fruit);
}
인덱스가 필요한 경우엔 일반 for문을 쓴다.
for (int i = 0; i < list.size(); i++) {
System.out.println(i + ": " + list.get(i));
}
ArrayList는 중간 삽입/삭제가 느리다. 예를 들어 100개짜리 리스트에서 첫 번째 요소를 삭제하면, 나머지 99개가 한 칸씩 앞으로 당겨진다. 데이터가 많을수록 이 비용이 커진다.
반면 순서대로 읽는 속도(인덱스 접근)는 빠르다. 인덱스로 바로 위치를 계산할 수 있기 때문이다.
중간 삽입/삭제가 자주 일어나는 상황이라면 LinkedList를, 데이터를 쌓고 순서대로 읽는 용도라면 ArrayList가 적합하다.
배열의 불편함을 거의 다 해소한 것이 ArrayList다. 크기를 신경 쓰지 않아도 되고, 추가/삭제/검색 메서드도 갖춰져 있다. 컬렉션을 처음 쓴다면 ArrayList부터 시작하는 게 자연스럽다.