Topic (오늘의 주제)
Java Collections Framework (JCF)
자바에서 데이터를 효율적으로 저장(Storage)하고 처리(Manipulation)하기 위해 제공하는 표준화된 인터페이스와 클래스의 집합입니다. 직접 자료구조를 구현할 필요 없이, 미리 만들어진 검증된 자료구조를 사용할 수 있게 해줍니다.
Why (왜 사용하는가? 왜 중요한가?)
이 개념이 실무, 설계, 면접에서 중요한 이유
- 실무: 데이터를 다루지 않는 프로그램은 없습니다. 상황(검색 위주 vs 삽입/삭제 위주)에 맞는 적절한 컬렉션을 선택해야 성능 저하를 막을 수 있습니다.
- 구조적 의미: 인터페이스와 구현의 분리를 통해 코드를 재사용하기 쉽고, 유지보수가 용이한 구조를 제공합니다. (예: ArrayList를 쓰다가 LinkedList로 바꿔도 코드 수정 최소화)
- 면접 의도: 자료구조(Data Structure)에 대한 이해도를 확인합니다. 특히 "ArrayList와 LinkedList의 차이"나 "HashMap의 동작 원리"는 면접 빈출 1순위 질문입니다.
Core Concept (핵심 개념 정리)
컬렉션 프레임워크는 크게 Collection 인터페이스(그룹)와 Map 인터페이스(키-값)로 나뉩니다.

| 구분 | 인터페이스 | 특징 (Key Point) | 대표 구현 클래스 |
| Collection | List | 순서 O, 중복 O 줄 세우기 |
ArrayList, LinkedList, Vector, Stack |
| Collection | Set | 순서 X, 중복 X 집합(주머니) |
HashSet, TreeSet, LinkedHashSet |
| Map | Map | 순서 X, Key 중복 X, Value 중복 O 키-값 쌍 (Key-Value) |
HashMap, TreeMap, LinkedHashMap, Hashtable |
💡 주요 구현체 특징
- ArrayList: 배열 기반. 조회(Get) 빠름, 중간 삽입/삭제 느림.
- LinkedList: 노드 기반. 중간 삽입/삭제 빠름, 조회 느림.
- HashSet: 가장 빠른 Set. 순서 보장 안 함.
- HashMap: 가장 많이 쓰이는 Map. Key를 해싱(Hashing)하여 저장.
Interview Answer Version (면접 답변식 요약)
"자바 컬렉션 프레임워크는 데이터를 효율적으로 저장하고 관리하기 위한 표준화된 클래스 집합입니다.
크게 순서가 있고 중복을 허용하는 List, 순서가 없고 중복을 허용하지 않는 Set, 그리고 키와 값의 쌍으로 데이터를 관리하는 Map 인터페이스로 나뉩니다.
실무에서는 데이터의 특성과 필요한 연산(검색, 삽입, 삭제)의 빈도에 따라 적절한 구현체를 선택하여 사용합니다."
Practical Tip (사용시 주의할 점 or 활용 예)
1. 실무 코드 습관 (DIP 적용)
변수를 선언할 때는 구현 클래스보다 인터페이스 타입을 사용하는 것이 좋습니다.
// Bad: 나중에 LinkedList로 바꾸려면 변수 타입을 다 바꿔야 함
ArrayList<String> list = new ArrayList<>();
// Good: 유연한 설계 (다형성 활용)
List<String> list = new ArrayList<>();
list = new LinkedList<>(); // 코드를 많이 수정하지 않고 구현체 교체 가능
2. 초기 용량(Capacity) 설정
데이터의 크기를 대략적으로 안다면 생성 시 크기를 지정하는 것이 성능에 좋습니다.
- new ArrayList<>(10000); : 배열 크기 재조정(Resizing) 비용 절감.
- new HashMap<>(10000); : 해시 충돌 및 재해싱(Rehashing) 비용 절감.
3. 동시성(Concurrency) 이슈
일반적인 ArrayList, HashMap은 Thread-Safe하지 않습니다. 멀티스레드 환경에서는 ConcurrentHashMap이나 Collections.synchronizedList() 등을 사용해야 합니다.
예상 꼬리질문 정리
- ArrayList와 LinkedList의 차이점은 무엇인가요?
- 답변 키워드: ArrayList는 배열 기반이라 인덱스 조회(Random Access)가 O(1)로 빠르지만, 중간 삽입/삭제는 O(n)으로 느립니다. LinkedList는 노드 연결 구조라 중간 삽입/삭제는 빠르지만, 조회는 순차 탐색이라 O(n)으로 느립니다.
- HashMap의 동작 원리와 해시 충돌(Hash Collision) 해결 방법은?
- 답변 키워드: Key의 hashCode()를 이용해 배열(Bucket)의 인덱스를 결정합니다. 충돌 발생 시 자바는 Chaining(연결 리스트 사용) 방식을 쓰며, Java 8부터는 데이터가 많아지면 Red-Black Tree로 변환해 성능을 최적화합니다.
- Set은 어떻게 중복을 걸러내나요?
- 답변 키워드: 객체의 hashCode()를 먼저 비교하고, 같으면 equals()로 한 번 더 비교하여 둘 다 같을 때만 중복으로 간주합니다. (따라서 VO 객체를 Set에 넣을 땐 두 메소드 모두 오버라이딩 필수)
공부하며 몰랐던 개념 (Deep Dive): 컬렉션 내부 구조와 성능의 비밀
단순히 List, Map 인터페이스만 보고 쓰는 것이 아니라, "실제 메모리 상에서 데이터가 어떻게 배치되길래 이런 성능 차이가 발생하는가?"를 이해하는 것이 핵심이다.
① ArrayList vs LinkedList: "연속된 아파트 vs 보물찾기 쪽지"
- 초보자의 오해: "둘 다 List니까 그냥 아무거나 쓰면 되는 거 아냐? 요즘은 컴퓨터가 빨라서 다 똑같지 않나?"
- 설계의 본질: 두 구현체는 데이터를 저장하는 물리적인 메모리 구조 자체가 완전히 다르다.
- ArrayList: 메모리 상에 빈틈없이 붙어있는 배열(Array) 구조다.
- LinkedList: 메모리 여기저기에 흩어져 있고, 서로가 서로를 가리키는 노드(Node) 구조다.
[🔍 구조적 차이: 조회(Get)의 원리]
- ArrayList (Random Access): 데이터가 연속되어 있으므로, 인덱스 5번을 찾으라고 하면 수학 공식(시작주소 + 5 * 데이터크기)으로 단 한 번의 계산만으로 위치를 찾는다. (O(1))
- LinkedList (Sequential Access): 데이터가 어디 있는지 모른다. 5번을 찾으려면 0번 노드에게 가서 "1번 어디 있어?" 묻고, 1번에게 "2번 어디 있어?" 물으며 건너가야 한다. (O(n))
[🔍 구조적 차이: 삽입/삭제(Insert/Delete)의 원리]
- ArrayList (이사 가는 고통): 중간에 데이터를 넣으려면, 그 뒤에 있는 모든 데이터를 한 칸씩 뒤로 밀어야(Shift) 한다. 꽉 차면 더 큰 배열을 만들고 이사(Copy)까지 가야 한다.
- LinkedList (사슬 끊기): 중간에 데이터를 넣으려면, 앞뒤 노드의 연결 고리(참조 주소)만 바꿔주면 끝이다. 데이터 이동이 전혀 없다.
[💡 실무 코드 품질: 언제 무엇을 써야 할까?]
// [ArrayList]
// 데이터 변경은 적고, 조회가 빈번한 경우 (대부분의 웹 게시판 목록, 조회 전용 데이터)
List<String> userList = new ArrayList<>();
String user = userList.get(999); // 🚀 번개같이 빠름
// [LinkedList]
// 데이터의 중간 삽입/삭제가 매우 빈번한 경우 (큐, 스택 구현, 실시간 데이터 스트림)
List<String> eventLog = new LinkedList<>();
eventLog.add(0, "New Event"); // 😎 기존 데이터 안 밀고 바로 끼워넣기 가능
② HashSet & HashMap: "배열과 리스트의 하이브리드(Hybrid)"
- 초보자의 오해: "Set은 순서가 왜 뒤죽박죽이지? Map은 어떻게 키만 알면 값을 바로 찾지? 마법인가?"
- 설계의 본질: HashMap과 HashSet은 내부적으로 **배열(Bucket)**을 기반으로 하되, 데이터의 고유값(HashCode)을 이용해 저장 위치를 바로 계산하는 구조다. (HashSet은 사실 껍데기일 뿐, 내부적으로 HashMap을 사용한다.)
[🔍 구조적 이유: 해싱(Hashing)과 버킷]
- 위치 결정: 데이터(Key)가 들어오면 hashCode()를 실행해 정수 값을 얻는다.
- 인덱스 매핑: 그 정수 값을 배열의 크기로 나눈 나머지(index = hash % size)를 이용해 배열의 몇 번째 방(Bucket)에 넣을지 결정한다.
- 이 과정이 수학적 계산이므로 반복문을 돌 필요 없이 즉시 위치를 찾는다 (O(1)).
- 순서가 뒤죽박죽인 이유는 데이터가 들어온 순서가 아니라, 키의 해시값(계산 결과)에 따라 방이 배정되기 때문이다.
[🔍 구조적 이슈: 해시 충돌(Collision)과 진화] 만약 서로 다른 키가 우연히 같은 방(Index)을 배정받으면 어떻게 될까?
- Java 7 이하: 같은 방에 들어온 데이터들을 LinkedList로 줄줄이 엮었다. (데이터가 많아지면 조회 속도가 O(n)으로 느려짐)
- Java 8 이상 (Deep Dive): 하나의 방(Bucket)에 데이터가 너무 많이(8개 이상) 쌓이면, 자동으로 **Red-Black Tree(이진 트리)**로 구조를 변경한다. 덕분에 최악의 경우에도 성능이 O(log n)으로 유지된다.
[💡 실무 코드 품질: Key 객체의 중요성]
// [HashMap의 Key로 사용할 객체 설계 시 주의점]
class UserKey {
String id;
// ⚠️ equals와 hashCode를 제대로 오버라이딩 하지 않으면?
// 논리적으로 같은 사람이어도 해시값이 달라서
// Map에서 데이터를 못 찾거나(Memory Leak), 중복 저장되는 치명적 버그 발생!
@Override
public int hashCode() {
return Objects.hash(id); // ID가 같으면 같은 방 번호를 줘야 함
}
@Override
public boolean equals(Object o) {
return this.id.equals(((UserKey)o).id); // ID가 같으면 같은 객체로 인식해야 함
}
}
결론 (Lesson Learned)
- ArrayList는 '책장에 꽂힌 책'과 같다. 몇 번째 책인지 알면 바로 꺼내지만, 중간에 책을 꽂으려면 나머지 책을 다 밀어야 한다. (조회 위주 실무 로직에 적합)
- LinkedList는 '보물찾기 쪽지'와 같다. 중간에 쪽지 내용을 바꾸긴 쉽지만, 마지막 장소를 찾으려면 처음부터 다 읽어야 한다. (삽입/삭제 위주 로직에 적합)
- **HashMap(Set)**은 내부적으로 **배열 + 링크드 리스트(or 트리)**의 합작품이다. 성능의 핵심은 "키를 얼마나 골고루 분산시키느냐(hashCode)"에 달려 있다.
'Archive > Daily Dev Q&A' 카테고리의 다른 글
| Daily Dev Q&A: 자바 제네릭 (0) | 2025.12.10 |
|---|---|
| Daily Dev Q&A: 자바의 접근 제어자(Access Modifier) (0) | 2025.12.09 |
| Deily Dev Q&A: SOLID원칙 (0) | 2025.12.08 |
| Daily Dev Q&A: 오버로딩과 오버라이딩 (0) | 2025.12.05 |
| Daily Dev Q&A: 다형성 (0) | 2025.12.03 |