기준

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로 감싼다.

관련 문서