본문으로 건너뛰기
김신건의 로그

[Java] List

· 수정 · 📖 약 2분 · 905자/단어 #java #collection #data-structure #list #interface
List, java.util.List, Java List, JList, 리스트, List interface

정의

java.util.List순서가 있고 중복이 허용 되는 컬렉션을 표현하는 인터페이스. 각 원소는 0 부터 시작하는 정수 인덱스로 접근 가능.

Collection 인터페이스를 확장하며, Iterable 의 모든 성질을 물려받는다. 대표 구현체로 ArrayList, LinkedList, Vector, CopyOnWriteArrayList 가 있다.

사용 상황

상황이유
순서가 있는 데이터 보관인덱스 기반 접근
중복이 허용되는 컬렉션Set 이 아닌 List
순차 순회Iterator / for-each
정렬 및 이진 검색Collections.sort, binarySearch
Stream 파이프라인 시작점list.stream()
배치 처리, 임시 버퍼동적 크기 조정

시각화

상속 계층

flowchart TD
    IT["Iterable"]
    COL["Collection"]
    LS["List (interface)"]
    AL["AbstractList"]
    ARR["ArrayList"]
    LL["LinkedList"]
    VEC["Vector"]
    COW["CopyOnWriteArrayList"]

    IT --> COL --> LS
    LS --> AL
    AL --> ARR
    AL --> VEC
    LS --> LL
    LS --> COW

핵심 메서드

메서드의미일반적 비용
get(int i)i 번째 원소 반환구현체 의존
set(int i, E e)i 번째 원소를 e 로 교체, 이전 값 반환구현체 의존
add(E e)끝에 추가, true 반환보통 O(1)
add(int i, E e)i 위치에 삽입구현체 의존
remove(int i)i 번째 원소 삭제, 그 값 반환구현체 의존
remove(Object o)첫 번째로 발견된 o 삭제O(n)
indexOf(Object o)첫 등장 인덱스, 없으면 -1O(n)
size()원소 개수O(1)
iterator(), listIterator()순회O(1) 생성
subList(int from, int to)부분 뷰 (백킹 리스트 공유)O(1)

구현체별 특징 한눈에

구현체내부 구조random access끝에 add앞/중간 삽입thread-safe
ArrayList동적 배열O(1)amortized O(1)O(n)
LinkedList양방향 연결 리스트O(n)O(1)O(1) (노드 있을 때) / O(n) (인덱스)
Vector동적 배열 + 메서드 동기화O(1)amortized O(1)O(n)✓ (메서드 단위)
CopyOnWriteArrayList불변 배열 + 쓰기 시 복사O(1)O(n) (전체 복사)O(n)

IMPORTANT

거의 모든 실무 코드에서 ArrayList 가 기본 선택. LinkedList 가 더 빠를 것 같은 상황 (앞 삽입) 도 실측하면 ArrayList 가 빠를 때가 많다. 캐시 친화성 차이 때문.

기본 사용

List<String> users = new ArrayList<>();
users.add("Alice");                    // [Alice]
users.add("Bob");                      // [Alice, Bob]
users.add(0, "Charlie");               // [Charlie, Alice, Bob]
users.set(1, "Dave");                  // [Charlie, Dave, Bob]
String first = users.get(0);           // "Charlie"
boolean has = users.contains("Bob");   // true
users.remove("Charlie");               // [Dave, Bob]

for (String u : users) {               // for-each (iterator)
    System.out.println(u);
}

정렬

List.sort()Collections.sort() 모두 내부적으로 TimSort (O(n log n)) 를 사용한다.

List<Integer> nums = new ArrayList<>(List.of(5, 2, 8, 1, 9));

// 자연 순서 오름차순
nums.sort(Comparator.naturalOrder());         // [1, 2, 5, 8, 9]

// 역순
nums.sort(Comparator.reverseOrder());         // [9, 8, 5, 2, 1]

// 커스텀: 길이 오름차순, 동점이면 사전 순
List<String> words = new ArrayList<>(List.of("banana", "fig", "apple"));
words.sort(Comparator.comparingInt(String::length)
                     .thenComparing(Comparator.naturalOrder()));
// [fig, apple, banana]

// 이진 검색 (정렬 선행 필수)
Collections.sort(nums);
int idx = Collections.binarySearch(nums, 5);  // 인덱스 반환

Stream API 와 함께

List<String> names = List.of("Alice", "Bob", "Charlie", "Dave");

// 필터 + 변환 + 수집
List<String> result = names.stream()
    .filter(n -> n.length() > 3)
    .map(String::toUpperCase)
    .sorted()
    .collect(Collectors.toList());
// [ALICE, CHARLIE, DAVE]

// 합계
int totalLen = names.stream()
    .mapToInt(String::length)
    .sum();   // 18

// 그룹화: Map<길이, 이름 리스트>
Map<Integer, List<String>> byLen = names.stream()
    .collect(Collectors.groupingBy(String::length));
// {3=[Bob], 4=[Dave], 5=[Alice], 7=[Charlie]}

// 플랫맵: 중첩 List 펼치기
List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3, 4));
List<Integer> flat = nested.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toList());   // [1, 2, 3, 4]

Collections 유틸리티

List<Integer> nums = new ArrayList<>(List.of(3, 1, 4, 1, 5));

Collections.reverse(nums);          // [5, 1, 4, 1, 3]
Collections.shuffle(nums);          // 무작위 섞기
int min = Collections.min(nums);    // 최솟값
int max = Collections.max(nums);    // 최댓값
Collections.fill(nums, 0);          // [0, 0, 0, 0, 0]

// nCopies: 같은 원소 n 개로 채운 불변 리스트
List<String> zeros = Collections.nCopies(5, "x");  // [x, x, x, x, x]

불변 (Immutable) List

Java 9 부터 List.of(...) 정적 메서드가 추가됐다.

List<Integer> primes = List.of(2, 3, 5, 7, 11);
primes.add(13);  // UnsupportedOperationException

// 기존 리스트의 불변 복사본
List<String> copy = List.copyOf(original);

List.of 는 null 원소도 허용하지 않는다. Arrays.asList 와 달리 크기 변경도 불가.

fail-fast 와 동시 수정

ArrayList, LinkedList, Vector 의 iterator 는 순회 중 구조가 변경되면 ConcurrentModificationException 을 던진다 (fail-fast iterator). 단일 스레드 안에서도 발생한다.

List<Integer> list = new ArrayList<>(List.of(1, 2, 3, 4));
for (Integer x : list) {
    if (x % 2 == 0) list.remove(x);  // ConcurrentModificationException
}

// Iterator.remove() 만 안전
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
    if (it.next() % 2 == 0) it.remove();
}

// Java 8+: removeIf (가장 간결)
list.removeIf(x -> x % 2 == 0);

CopyOnWriteArrayList 만 예외, snapshot iterator 라 CME 가 발생하지 않는다.

함정

1. remove(int) vs remove(Object) 오버로드 혼동

List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
list.remove(1);                      // int 오버로드: 인덱스 1 삭제 → [1, 3]
list.remove(Integer.valueOf(1));     // Object 오버로드: 값 1 삭제 → [2, 3]

WARNING

List<Integer> 에서 remove(1)값 1이 아닌 인덱스 1 을 삭제한다. 값을 제거하려면 remove(Integer.valueOf(x)) 또는 removeIf(v -> v == x).

2. subList 는 원본을 공유

List<String> full = new ArrayList<>(List.of("a", "b", "c", "d"));
List<String> sub = full.subList(1, 3);   // [b, c] (뷰)
sub.clear();                              // full = [a, d]

subList() 반환값을 수정하면 원본도 바뀐다. 독립 복사본이 필요하면 new ArrayList<>(sub).

3. Arrays.asList 는 크기 고정

List<String> fixed = Arrays.asList("x", "y", "z");
fixed.set(0, "X");   // OK: 원소 교체 가능
fixed.add("w");      // UnsupportedOperationException: 크기 변경 불가

List.of 와 달리 원소 교체는 가능하지만 add / remove 는 불가.

4. Collections.sort 는 in-place, Stream.sorted 는 새 리스트

List<Integer> a = new ArrayList<>(List.of(3, 1, 2));
List<Integer> b = a.stream().sorted().collect(Collectors.toList());
// a 는 그대로 [3, 1, 2], b 는 [1, 2, 3]

Collections.sort(a);
// a 는 [1, 2, 3] (in-place 정렬)

어떤 구현체를 골라야 하나

상황권장
기본, 의심스러우면ArrayList
앞/뒤 빈번 삽입/삭제, Deque 로도 쓰고 싶다LinkedList (인덱스 접근 거의 없을 때)
레거시 코드 호환Vector (새 코드에서는 피한다)
읽기 多 / 쓰기 거의 없음, 동시성CopyOnWriteArrayList
불변 데이터List.of(...), List.copyOf(...)
일반적 동시성Collections.synchronizedList(new ArrayList<>())

관련 위키

이 글의 용어 (9개)
[Java] ArrayListjava
정의 는 동적 배열 (dynamic array) 로 구현된 . 내부적으로 를 들고 다니며, 용량이 부족해지면 새 배열로 옮긴다. Java 의 가장 흔한 컬렉션 구현체. 인덱스 기…
[Java] Collectionjava
정의 는 그룹으로 묶인 객체들을 표현하는 최상위 인터페이스. JCF (Java Collections Framework) 의 입구이자, / / / 모두 이를 확장한다. 자체는 직접…
[Java] ConcurrentModificationExceptionjava
정의 (CME) 는 fail-fast iterator 가 컬렉션의 구조 변경을 탐지했을 때 던지는 unchecked 예외 ( ). 이름과 달리 반드시 동시성 (멀티스레드) 문제일…
[Java] CopyOnWriteArrayListjava
정의 는 쓰기 시 배열 전체를 복사 하는 thread-safe 구현. 읽기에는 lock 이 전혀 없고, 쓰기에는 으로 직렬화한다. (JSR-166) 의 컬렉션. 읽기 압도적 다,…
[Java] Fail-Fast Iteratorjava
정의 Fail-Fast Iterator 는 순회 도중 컬렉션의 구조가 변경되면 즉시 을 던지는 iterator. , , 등 대부분의 컬렉션의 iterator 가 이 방식. 목적은…
[Java] Iterablejava
정의 는 루프로 순회 가능한 모든 타입의 최상위 인터페이스. 단 하나의 추상 메서드, 를 정의한다. 인터페이스가 을 extends 하므로 , , , 등 모든 컬렉션이 자동으로 대…
[Java] LinkedListjava
정의 는 양방향 연결 리스트 (doubly linked list) 로 구현된 이자 . 각 원소는 와 포인터를 가진 노드로 보관된다. 와 같은 List 이지만, 메모리 레이아웃과 …
집합과 맵 (Set and Map)algorithm
정의 집합 (Set) 은 고유한 원소들의 모임을 나타내는 추상 자료구조로, 멤버십 검사, 삽입, 삭제를 지원한다. 맵 (Map, Dictionary) 은 키-값 쌍을 저장하며, …
Geometry Basic: 벡터, CCW, 외적, 내적algorithm
정의 기하 알고리즘의 밑바탕. 대부분의 2D/3D 기하 문제는 벡터 연산, 내적/외적, CCW 판정 세 가지의 조합으로 풀립니다. 문제 상황 PS 기하에서 자주 마주치는 질문들:…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기