기준
List는 "순서가 있는 배열형 컬렉션"이다. 값 조회보다 인덱스, 삭제 비용, 크기 변경 가능 여부를 먼저 확인한다.
사례 지도
| 장면 | 실수 | 다음 기준 |
|---|---|---|
| 값 1을 지우려 함 | remove(1)로 인덱스 1 삭제 | 값 삭제는 Integer.valueOf(1) |
| 후보를 계속 제거 | ArrayList 중간 삭제 반복 | 남길 값만 새 리스트에 담기 |
| 존재 여부 확인 | contains() 반복 호출 | HashSet으로 옮긴 뒤 조회 |
| 첫 값 꺼내기 | 빈 리스트에서 get(0) | 필터 결과는 항상 비어 있을 수 있음 |
| 배열을 리스트로 변환 | Arrays.asList()에 add() | 크기 변경이면 new ArrayList<>() |
flowchart TD
Need["List를 쓰려는 이유"] --> Ordered{"순서가 필요한가"}
Ordered -->|아니오| Set["Set이나 Map 검토"]
Ordered -->|예| Operation{"반복 작업은 무엇인가"}
Operation -->|인덱스 조회| ListOK["List 적합"]
Operation -->|존재 확인 반복| HashSet["HashSet으로 보조"]
Operation -->|중간 삭제 반복| Rebuild["새 List로 재구성"]
Operation -->|앞뒤 삽입 삭제| Deque["Deque 검토"]
값 삭제인 줄 알고 인덱스를 삭제
증상
List<Integer>에서 값 1을 지우려 했는데 두 번째 원소가 삭제된다.
List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
list.remove(1); // 인덱스 1번인 2가 삭제된다
고치는 기준
list.remove(Integer.valueOf(1));
remove(int index)와 remove(Object value)가 오버로드되어 있다. 숫자 리터럴을 넣으면 인덱스 삭제로 해석된다.
중간 삭제를 반복해서 느려짐
장면
조건에 맞지 않는 값을 지우려고 ArrayList를 순회하면서 계속 remove()한다.
for (int i = 0; i < list.size(); i++) {
if (list.get(i) < 0) {
list.remove(i);
}
}
ArrayList는 중간 원소가 삭제될 때 뒤 원소를 당긴다. 반복 삭제가 많으면 O(N^2)이 되기 쉽다.
고치는 기준
List<Integer> next = new ArrayList<>();
for (int value : list) {
if (value >= 0) {
next.add(value);
}
}
필터링이 목적이면 삭제보다 재구성이 읽기 쉽고 안전하다.
contains()를 반복해서 시간초과
List.contains()는 내부를 순회한다. 바깥 반복문과 만나면 바로 O(N^2)이 된다.
| 코드 | 조회 비용 | 반복 시 위험 |
|---|---|---|
list.contains(x) | O(N) | 큼 |
set.contains(x) | 평균 O(1) | 낮음 |
Set<Integer> exists = new HashSet<>(list);
if (exists.contains(target)) {
// 존재 여부만 필요할 때
}
Arrays.asList()를 일반 리스트처럼 사용
증상
배열을 리스트로 바꾼 뒤 add()를 호출하면 실패한다.
List<Integer> list = Arrays.asList(1, 2, 3);
list.add(4); // UnsupportedOperationException
고치는 기준
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
list.add(4);
크기를 바꿀 가능성이 있으면 ArrayList로 감싼다.