JCF
contents
Java Collections Framework (JCF) 는 객체의 그룹(컬렉션)을 표현하고 조작하기 위한 통일된 아키텍처입니다. 자바 개발자, 특히 백엔드 개발자에게는 가장 기초적이고 필수적인 도구 상자입니다.
핵심은 정렬이나 검색 알고리즘을 밑바닥부터 직접 짤 필요 없이, 데이터를 효율적으로 처리할 수 있도록 표준화된 인터페이스(Interface, 추상 데이터 타입) 와 구현체(Implementation, 재사용 가능한 자료구조) 를 제공한다는 점입니다.
1. 큰 그림: 계층 구조 (Hierarchy) 🏛️
JCF는 java.util 패키지에 정의되어 있습니다. 크게 두 개의 메인 트리로 나뉩니다.
Collection인터페이스: 객체들의 그룹 (Lists, Sets, Queues).Map인터페이스: 키-값(Key-Value) 쌍의 그룹. (주의:Map은Collection을 상속받지 않습니다).
2. Collection 계열 📦
루트 인터페이스인 Collection은 Iterable을 상속받습니다(즉, 향상된 for-each 루프를 사용할 수 있습니다).
A. List (순서 있음, 중복 허용)
List는 인덱스(0, 1, 2...)를 중요하게 생각합니다. 데이터가 들어온 순서(Insertion order)를 유지합니다.
ArrayList(기본 선택)- 구현: 크기가 조절되는 동적 배열(Resizable dynamic array).
- 성능:
- 접근 (
get): $O(1)$ - 매우 빠름. - 끝부분 삽입/삭제: $O(1)$ (분할 상환 분석 기준).
- 중간 삽입/삭제: $O(n)$ - 느림 (요소들을 한 칸씩 밀어야 함).
- 접근 (
- 용도: 90%의 경우에 사용. 읽기 작업이 많은 경우.
LinkedList- 구현: 이중 연결 리스트(Doubly-linked list).
- 성능:
- 접근 (
get): $O(n)$ - 느림 (노드를 타고 이동해야 함). - 삽입/삭제: $O(1)$ - 빠름 (포인터만 바꾸면 됨), 단 삽입할 위치를 찾는 시간은 별도.
- 접근 (
- 용도: 리스트의 중간 에서 삽입/삭제가 빈번한 경우. (현대 백엔드에서는 ArrayList보다 캐시 지역성(Cache locality)이 떨어져서 잘 사용되지 않음).
Vector(레거시)ArrayList와 비슷하지만 동기화(Synchronized) 되어 있습니다(스레드 안전). 느립니다. 쓰지 마세요. 대신ArrayList나CopyOnWriteArrayList를 사용하세요.
B. Set (유일성, 중복 불가)
Set은 수학의 집합을 모델링합니다. 중복된 요소(e1.equals(e2))를 허용하지 않습니다.
HashSet(기본 선택)- 구현: 내부적으로
HashMap을 사용. - 순서: 순서가 없음(Unordered). 요소의 순서를 예측할 수 없음.
- 성능: 추가, 삭제, 포함 여부 확인에 $O(1)$.
- 요구사항: 여기에 저장되는 객체는 반드시
hashCode()와equals()를 올바르게 재정의(Override)해야 함.
- 구현: 내부적으로
LinkedHashSet- 구현: 해시 테이블 + 연결 리스트.
- 순서: 입력 순서(Insertion order) 를 유지함.
- 성능: 링크를 유지해야 하므로
HashSet보다 약간 느림.
TreeSet- 구현: 레드-블랙 트리 (자가 균형 이진 탐색 트리).
- 순서: 정렬됨(Sorted) (자연 순서 또는
Comparator사용). - 성능: $O(\log n)$. HashSet보다 느리지만 데이터가 항상 정렬되어 있어야 할 때 유용.
C. Queue & Deque (처리 대기열)
처리를 기다리는 요소들을 담아두는 데 사용됩니다 (FIFO - 선입선출).
PriorityQueue- 구현: 우선순위 힙(Priority Heap).
- 동작: 우선순위에 따라 정렬됨. 항상 가장 작은 요소(또는
Comparator기준)가 "헤드(Head)"에 옴. - 용도: 작업 스케줄링, 길 찾기 알고리즘(다익스트라).
ArrayDeque(배열 양방향 큐)- 동작: 양쪽 끝에서 추가/제거 가능.
- 성능:
Stack(레거시)이나LinkedList보다 빠름. 스택(LIFO)이나 큐(FIFO)가 필요할 때 이것을 사용하세요.
3. Map 계열 🗺️
Map은 키-값(Key-Value) 쌍으로 작동합니다. 키는 유일해야 하며, 값은 중복될 수 있습니다.
HashMap(기본 선택)- 구현: "버킷(Buckets)"의 배열 (내부적으로 연결 리스트 또는 트리 사용).
- 동작: 키의 순서가 없음. 하나의
null키 허용. - 성능: get/put에 $O(1)$.
- 내부 동작: 키의
hashCode()를 사용하여 버킷 인덱스를 찾음. 두 키의 해시가 같으면(충돌), 리스트로 연결함 (Java 8부터는 리스트가 길어지면 레드-블랙 트리로 변환).
LinkedHashMap- 동작: 입력 순서(또는 접근 순서)를 유지. LRU (최근 사용) 캐시를 만들 때 훌륭함.
TreeMap- 구현: 레드-블랙 트리.
- 동작: 키가 정렬됨.
- 성능: $O(\log n)$.
- 용도: 범위 검색이 필요할 때 (예: "ID가 100에서 200 사이인 모든 사용자 검색").
Hashtable(레거시)HashMap과 같으나 동기화되어 있음. 쓰지 마세요. 대신ConcurrentHashMap을 사용하세요.
4. 헬퍼 클래스 (유틸리티) 🛠️
자료구조만 사용하는 것이 아니라, 이를 조작하는 도구들도 사용합니다.
java.util.Collections: 정적(static) 메서드들을 모아둔 유틸리티 클래스.Collections.sort(list): 리스트 정렬 (MergeSort/TimSort).Collections.shuffle(list): 순서 섞기.Collections.synchronizedList(list): 리스트를 스레드 안전하게 감쌈 (기본적인 락 사용).Collections.unmodifiableList(list): 읽기 전용 뷰 생성.
java.util.Arrays:Arrays.asList(...): 배열을 고정 크기 리스트로 변환.
5. 반복(Iteration)과 "Fail-Fast" 🔄
이들을 어떻게 순회할까요?
- Iterator: 표준 순회 방법.
Iterator it = list.iterator();
while(it.hasNext()) {
String s = it.next();
if(s.equals("bad")) it.remove(); // 안전한 삭제
}
- Fail-Fast 동작: 한 스레드가 컬렉션을 순회(Iterator 사용)하는 동안 다른 스레드가 컬렉션을 수정(추가/삭제)하면, Iterator는 즉시
ConcurrentModificationException을 발생시킵니다. 이는 예측 불가능한 버그를 막기 위한 설계입니다.
6. 백엔드 개발자를 위한 요약
| 인터페이스 | 구현체 | 순서 | 중복? | 추천 용도 |
|---|---|---|---|---|
| List | ArrayList |
입력 순서 | O | 일반적인 용도, 랜덤 액세스. |
LinkedList |
입력 순서 | O | 양 끝에서 빈번한 추가/삭제. | |
| Set | HashSet |
랜덤 | X | "이게 존재하는가?" 확인용, 중복 제거. |
TreeSet |
정렬됨 | X | 정렬된 유일 데이터. | |
| Map | HashMap |
랜덤 | 키: X | 키-값 조회, 캐싱. |
TreeMap |
키 정렬됨 | 키: X | 키 범위 검색. |
동시성(Concurrency) 주의사항
표준 JCF(ArrayList, HashMap)는 스레드 안전(Thread-safe)하지 않습니다.
백엔드 환경(Spring Boot 요청 처리 등)에서 여러 스레드가 동시에 컬렉션에 접근한다면 반드시 다음을 사용해야 합니다:
java.util.concurrent패키지:ConcurrentHashMap(Hashtable/SynchronizedMap 대신 사용).CopyOnWriteArrayList(스레드 안전한 List).BlockingQueue(생산자-소비자 패턴용).
references