1. 클래스 ArrayList
Java에서 인터페이스 List는 다음과 같은 계약입니다: “저는 정렬된 요소 컬렉션이며, 인덱스로 요소를 가져올 수 있습니다”.
리스트(List)의 핵심 특성:
- 요소가 특정 순서로 저장됩니다(집합과 달리).
- 중복 요소를 저장할 수 있습니다(예: 두 번 "Vasya").
- 인덱스로 요소에 접근할 수 있습니다: 첫 번째 요소의 인덱스는 0, 두 번째는 1 등입니다.
- 리스트의 어느 위치에나 요소를 추가하고 제거할 수 있습니다.
인터페이스 List의 가장 인기 있는 구현은 ArrayList입니다. 내부적으로 일반 배열을 사용하며, 데이터가 늘어나면 자동으로 확장됩니다.
ArrayList 생성 방법
import java.util.ArrayList;
import java.util.List;
public class Example {
public static void main(String[] args) {
// 문자열 리스트를 생성합니다
List<String> students = new ArrayList<>();
}
}
설명
- List<String>는 “문자열 리스트” 타입의 변수입니다. 변수는 인터페이스로 선언하고, 객체는 구체 클래스(ArrayList)로 생성합니다.
- 꺾쇠 괄호 <String>는 리스트 요소의 타입을 지정합니다(제네릭, generics).
ArrayList(및 모든 List)의 기본 메서드:
- add(element) — 요소를 리스트 끝에 추가합니다.
- add(index, element) — 지정된 인덱스에 요소를 삽입합니다.
- get(index) — 인덱스로 요소를 가져옵니다.
- set(index, element) — 인덱스 위치의 요소를 교체합니다.
- remove(index) — 인덱스로 요소를 삭제합니다.
- remove(Object) — 전달된 객체와 같은 첫 번째 요소를 삭제합니다.
- size() — 리스트의 요소 개수를 확인합니다.
예시: 학생 목록 다루기
import java.util.ArrayList;
import java.util.List;
public class StudentListDemo {
public static void main(String[] args) {
List<String> students = new ArrayList<>();
// 학생 추가
students.add("Vasya");
students.add("Petya");
students.add("Masha");
// 모든 학생 출력
System.out.println("학생 목록: " + students);
// 첫 번째 학생 가져오기
String first = students.get(0);
System.out.println("첫 번째 학생: " + first);
// 두 번째 학생의 이름 변경
students.set(1, "Pavel");
System.out.println("변경 후: " + students);
// Masha 삭제
students.remove("Masha");
System.out.println("Masha 삭제 후: " + students);
// 리스트 크기
System.out.println("총 학생 수: " + students.size());
}
}
결과:
학생 목록: [Vasya, Petya, Masha]
첫 번째 학생: Vasya
변경 후: [Vasya, Pavel, Masha]
Masha 삭제 후: [Vasya, Pavel]
총 학생 수: 2
우리 애플리케이션과 어떻게 연결될까요?
학생 과제를 관리하는 애플리케이션이 있다고 가정해 봅시다. 이제 배열 대신 List<String>로 과제 목록을 저장하고, 동적으로 새로운 과제를 추가할 수 있습니다.
2. 클래스 LinkedList: 삽입/삭제 속도가 중요할 때
LinkedList는 리스트의 또 다른 구현입니다. 이중 연결된 체인으로 구성되어 각 노드는 이전과 다음 요소를 알고 있습니다. 마치 열차 편성처럼, 중간에 “객차”를 넣어도 전체를 재배열하지 않고 빠르게 처리할 수 있습니다.
LinkedList 생성
import java.util.LinkedList;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<String> tasks = new LinkedList<>();
}
}
LinkedList의 특징
- 리스트의 시작과 중간에서 삽입과 삭제가 빠릅니다.
- 인덱스로 접근하는 것은 느립니다(100번째 요소를 찾으려면 체인을 따라가야 함).
- 끝뿐 아니라 시작이나 중간에도 자주 요소를 추가/삭제한다면 적합합니다.
예시: LinkedList 사용
import java.util.LinkedList;
import java.util.List;
public class TaskListDemo {
public static void main(String[] args) {
List<String> tasks = new LinkedList<>();
tasks.add("일어나기");
tasks.add("아침 먹기");
tasks.add("수업 들으러 가기");
// 작업을 맨 앞에 추가
tasks.add(0, "알람 설정하기");
System.out.println("작업 목록: " + tasks);
// 첫 번째 작업(가장 이른 작업) 삭제
tasks.remove(0);
System.out.println("첫 번째 작업 삭제 후: " + tasks);
}
}
3. ArrayList와 LinkedList 비교
| 기준 | |
|
|---|---|---|
| 기반 | 배열 | 이중 연결 리스트 |
| 인덱스로 빠른 접근 | 예 (O(1)) | 아니요 (O(n)) |
| 시작/중간에서 빠른 삽입/삭제 | 아니요 (O(n)) | 예 (O(1) — 참조를 알고 있는 경우) |
| 끝에서 빠른 삽입/삭제 | 예 (보통 O(1)) | 예 (O(1)) |
| 메모리 | 메모리 사용량이 적음 | 더 많음(이웃 노드에 대한 추가 참조) |
| 일반적인 용도 | 인덱스 기반 접근이 빈번함 | 삽입/삭제가 빈번함 |
간단한 규칙:
- 인덱스로 빠르게 접근해야 한다면 ArrayList를 사용하세요.
- 시작이나 중간에 자주 추가/삭제한다면 LinkedList를 사용하세요.
4. 리스트에서 자주 쓰는 연산
리스트 순회
기본 for 루프
for (int i = 0; i < students.size(); i++) {
System.out.println("학생 #" + i + ": " + students.get(i));
}
for-each 루프(가장 흔한 방법)
for (String name : students) {
System.out.println("이름: " + name);
}
람다 표현식(Java 8+)
students.forEach(name -> System.out.println("이름: " + name));
요소 검색
- contains(element) — 요소가 리스트에 있으면 true를 반환합니다.
- indexOf(element) — 요소가 처음 나타나는 인덱스를 반환하고, 없으면 -1을 반환합니다.
if (students.contains("Vasya")) {
System.out.println("Vasya가 목록에 있습니다!");
}
int index = students.indexOf("Vasya");
System.out.println("Vasya의 인덱스: " + index);
리스트 비우기
clear() — 모든 요소를 삭제합니다.
students.clear();
System.out.println("비운 후 목록: " + students);
5. ArrayList는 언제 쓰고, LinkedList는 언제 쓸까?
다음처럼 생각하는 게 쉽습니다: ArrayList는 인덱스로 요소에 빠르게 접근해야 하고, 리스트가 자주 변경되지 않을 때 좋습니다. 예를 들어, 긴 사용자 목록이나 메시지 기록처럼 대부분 읽기 위주인 경우입니다.
반대로 LinkedList는 시작/중간에 요소를 자주 삽입하거나 삭제할 때 유용합니다. 큐, 스택, 실행 취소 기록 등의 사례가 이에 해당합니다.
실무에서는 대부분 ArrayList를 사용합니다. LinkedList는 “비상용 도구”처럼 서랍에 넣어두었다가, 가끔 꼭 필요한 상황에서 쓰게 됩니다.
6. 리스트 작업 시 흔한 실수
실수 №1: 리스트 범위를 벗어남. 가장 흔한 상황은 존재하지 않는 인덱스로 접근하는 것입니다. 리스트에 요소가 세 개뿐인데 students.get(5)를 호출하면 IndexOutOfBoundsException이 발생합니다. 접근 전에 size()를 확인하세요.
실수 №2: 순회 중 요소 삭제. for-each로 순회하면서 동시에 요소를 삭제하면 ConcurrentModificationException이 발생합니다. 복잡한 삭제가 필요하면 인덱스 기반 루프나 Iterator를 사용하세요.
실수 №3: 객체 비교를 잘못함. 사용자 정의 객체(예: Student)를 저장하는 경우, contains와 remove는 equals에 의존합니다. 이를 재정의하지 않으면 내용이 아니라 참조로 비교합니다.
실수 №4: raw 타입 사용. List list = new ArrayList()처럼 작성하지 말고, 항상 요소 타입을 명시하세요: List<String> list = new ArrayList<>(). 제네릭은 오류를 방지하고 코드를 더 이해하기 쉽게 해줍니다.
GO TO FULL VERSION