List는 배열의 한계로 만들어진 자료형이다. 배열은 크기가 정해져 있지만, 프로그래밍을 하다 보면 크기를 알 수 없는 상황이 발생한다. 이 경우, List 를 사용하면 동적으로 자료형의 개수를 늘릴 수 있다. 리스트는 배열과 같이 객체를 일렬로 늘어놓은 구조를 갖는다. 객체를 index로 관리하기 때문에 객체를 저장하면 자동으로 인덱스가 부여되고, 인덱스를 기준으로 객체를 검색, 추가, 삭제할 수 있는 등의 기능을 제공한다.
List 인터페이스를 구현한 대표적인 클래스에는 ArrayList, LinkedList, Vector가 있으며, Stack은 Vector를 상속받아 스택 구조(LIFO)를 지원하는 클래스이다.
ArrayList는 내부적으로 배열을 사용하여 요소를 저장하며, 동적으로 크기가 조절된다. 랜덤 액세스(인덱스를 통한 요소 접근)가 빠르지만, 중간에 요소를 추가 또는 제거할 때는 비효율적일 수 있다.
따라서 조회를 많이 하는 경우에 사용하는 것이 좋다.
LinkedList는 Double linked list(이중 연결 리스트) 구조를 사용하여 요소를 저장하는 리스트 구현체이다. 각 노드는 데이터와 다음노드의 주소, 이전 노드의 주소를 갖는다.
따라서 삽입/삭제가 빈번한 경우, 순차적 접근이 많은 경우에 사용하는 것이 좋다.
두 리스트는 모두 List 인터페이스를 구현하지만, 내부 동작 방식이 달라 성능 차이가 존재한다.
| ArrayList | LinkedList | |
| add(E element) | ✅ O(1) (끝에 추가) | ✅ O(1) (끝에 추가) |
| add(int index, E element) | ❌ O(n) (중간 삽입 시 밀어내기) | ✅ O(n) (이동 + 노드 연결만 하면 됨) |
| addAll(Collection<? extends E>) | ✅ O(m) (추가할 요소 수 m만큼) | ✅ O(m) |
| clear() | ✅ O(1) | ✅ O(1) |
| contains(Object o) | ❌ O(n) (순차 탐색) | ❌ O(n) (노드 순회) |
| elementAt(int index) | ✅ O(1) (배열 접근) | ❌ O(n) (앞에서부터 순회) |
| get(int index) | ✅ O(1) | ❌ O(n) |
| indexOf(Object o) | ❌ O(n) | ❌ O(n) |
| isEmpty() | ✅ O(1) | ✅ O(1) |
| remove(int index) | ❌ O(n) (뒤로 밀어야 함) | ✅ O(n) (노드만 끊어주면 됨) |
| remove(Object o) | ❌ O(n) (탐색 + 삭제) | ❌ O(n) (탐색 + 연결 해제) |
| size() | ✅ O(1) | ✅ O(1) |
| toArray() | ✅ O(n) | ✅ O(n) |
| sort(Comparator) | ✅ O(n log n) (내부 정렬 지원) | ❌ O(n log n) (Array로 바꿔 정렬함) |
| Array | ArrayList | |
| 크기 변경 | ❌ 고정 | ✅ 동적 증가/감소 |
| 제네릭 지원 | ❌ 불가능 | ✅ 가능 (List<Integer>) |
| 메서드 제공 | ❌ 없음 (기본 연산만) | ✅ 추가, 삭제, 검색 등 다수 |
| 사용 난이도 | 낮음 (직관적) | 높음 (컬렉션 API 사용) |
📌 사용 상황
| ArrayList | LinkedList | |
| 내부 구조 | 배열 (Object[]) | 이중 연결 리스트 (노드) |
| 접근 속도 | ✅ 빠름 (O(1)) | ❌ 느림 (O(n)) |
| 삽입/삭제 | ❌ 느림 (중간에선 O(n)) | ✅ 빠름 (O(1)) |
| 메모리 사용량 | 낮음 | 높음 (포인터 추가 저장) |
| 활용 구조 | 일반 리스트, 조회 중심 | 큐, 덱, 삽입/삭제 중심 구조 |
| Array | 고정 크기, 빠른 접근 | 크기 변경 없고 단순한 구조일 때 |
| ArrayList | 동적 배열, 빠른 조회, 느린 삽입/삭제 | 조회 중심, 리스트 UI 렌더링 등 |
| LinkedList | 노드 기반 연결 구조, 삽입/삭제 빠름 | 큐, 덱, 삽입/삭제 많은 경우 |
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
📌 Tip: LinkedList에서는 되도록 for-each나 iterator 사용이 더 효율적
for (String fruit : list) {
System.out.println(fruit);
}
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
String fruit = iter.next();
if (fruit.equals("banana")) {
iter.remove(); // 안전하게 삭제 가능
}
}
| 삭제 가능 | 속도(ArrayList) | 속도(LinkedList) | 특이사항 | |
| for (i) | ✅ (주의) | 빠름 (O(1)) | 느림 (O(n)) | 삭제 시 인덱스 조정 필요 |
| for-each | ❌ | 보통 | 보통 | 삭제 시 예외 발생 가능 |
| iterator | ✅ | 빠름 | 빠름 | 삭제 가능, 안전함 ✅ |
cf) List.stream().forEach(), removeIf()
- 자바 8 이후 추가된 함수형 스타일의 컬렉션 처리 방식.(이는 나중에 정리하겠다~)
참고
- https://sugoring-it.tistory.com/258
- https://pongic.tistory.com/3
- https://velog.io/@hameee/Java-List