F-LAB JAVA · 3주차 · Phase 2 · 컬렉션 프레임워크 전체 지도
🚀 Phase 2 시작 — 1·2주차에서 본 컬렉션의 전체 풍경 완성
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
Iterable → Collection → List/Set/Queue 인터페이스 계층은?Map 이 왜 Collection 의 자식이 아닌가?컬렉션은 "배열의 한계를 메우기 위해 등장한 자료구조 + 알고리즘의 프레임워크" 다.
배열은 가장 기본이지만, 크기 고정 · 삽입/삭제 불편 · 단일 타입 등의 한계가 명확.
자바 1.2 (1998) 의 컬렉션 프레임워크는 이 한계를 인터페이스로 추상화 해서,
박승제씨가 매일 쓰는List<Shipment>,Map<Long, Cargo>같은 패턴의 토대를 만들었다.
| 시스템 | 비유 |
|---|---|
배열 (int[]) | 단일 칸막이 상자 — 같은 크기, 같은 종류만 |
컬렉션 (List<Shipment>) | 모듈형 도구 상자 — 종류·크기 유연, 다양한 도구 |
| List | 순서 있는 도구 (드라이버 1, 2, 3번) |
| Set | 중복 없는 도구 모음 (서로 다른 종류) |
| Queue | 순번 줄 (선착순 처리) |
| Map | 라벨이 붙은 서랍 (이름으로 찾기) |
→ 배열은 하드웨어 자료구조, 컬렉션은 소프트웨어 추상화.
1. 배열의 본질 — 메모리 레벨 정리
2. 배열의 5가지 한계
3. 컬렉션 프레임워크의 등장 (Java 1.2, 1998)
4. 자바 컬렉션 계층 다이어그램
5. Collection 과 Map 의 분리
6. List/Set/Queue 의 의미적 차이
7. ILIC 실무 — 자료구조 선택 출발점
8. 흔한 실수 + 안티패턴
9. 면접 + 자기 점검
int[] arr = new int[10]; // 길이 10 인 int 배열
arr[0] = 100; // 인덱스로 접근
arr[1] = 200;
배열의 본질:
int[] arr = new int[5];
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
Heap 메모리:
int[] 객체:
┌─────────────────────────────────────────────────────┐
│ Header (Mark Word + Class Pointer) │ 16 bytes
├─────────────────────────────────────────────────────┤
│ Length: 5 │ 4 bytes
├─────┬─────┬─────┬─────┬─────┬─────────────────────┤
│ 10 │ 20 │ 30 │ 0 │ 0 │ Padding │ 4×5 + ?
└─────┴─────┴─────┴─────┴─────┴─────────────────────┘
[0] [1] [2] [3] [4]
주소: 0xAA00, 0xAA04, 0xAA08, 0xAA0C, 0xAA10
→ 연속된 메모리
→ 박승제씨가 2주차 Unit 1.5 (Object Header) 에서 본 패턴과 동일.
1. 빠른 인덱스 접근
arr[i] → O(1)
주소 계산: 시작 주소 + i × element_size
2. 메모리 효율
- 헤더 외에 추가 메타데이터 없음
- 연속 메모리로 캐시 효율 ↑
- 작은 메모리 사용
3. 단순함
- 자료구조의 기본
- JVM 이 직접 지원
- 모든 다른 자료구조의 토대
4. CPU 캐시 친화적
- 연속 메모리 → L1/L2/L3 캐시 활용
- 순회 매우 빠름
// primitive 배열
int[] ints = new int[10];
double[] doubles = new double[5];
// 객체 배열
String[] strings = new String[10];
Shipment[] shipments = new Shipment[5];
// 다차원 배열 (배열의 배열)
int[][] matrix = new int[3][4];
박승제씨가 알아둘 것:
length 필드 (메서드 아님)String[] 은 Object[] 로 취급박승제씨가 2주차 Unit 5.2 에서 본 ArrayList:
public class ArrayList<E> {
transient Object[] elementData; // 내부는 배열!
private int size;
// ...
}
→ ArrayList 는 "배열을 감싼 동적 컬렉션".
→ 1.5배 확장으로 가변 크기 흉내.
// 배열 사용 — 거의 안 함
public class ShipmentBatchProcessor {
Shipment[] shipments = new Shipment[100]; // ❌ 어색
}
// 컬렉션 사용 — 표준
public class ShipmentBatchProcessor {
List<Shipment> shipments = new ArrayList<>(); // ✓ 표준
}
박승제씨가 ILIC 에서 배열을 직접 쓸 일:
String[] PORTS = {"BUSAN", "SEOUL"})배열이 컬렉션보다 빠른 이유와 그래도 컬렉션을 쓰는 이유는?
답:
배열이 빠른 이유:
그래도 컬렉션 쓰는 이유:
→ 유지보수성 > 약간의 성능.
int[] arr = new int[10];
arr[10] = 100; // ❌ ArrayIndexOutOfBoundsException
배열은 생성 시 크기 결정, 이후 변경 불가.
해결책:
int[] old = new int[10];
// ... 사용
int[] bigger = new int[20];
System.arraycopy(old, 0, bigger, 0, 10); // 수동 복사
→ 매번 이런 코드는 비효율.
→ ArrayList 의 1.5배 확장은 이 작업을 자동화.
int[] arr = {1, 2, 3, 4, 5};
// 인덱스 2 에 99 삽입하려면?
// arr.insert(2, 99); ← ❌ 그런 메서드 없음
// 수동으로:
int[] result = new int[arr.length + 1];
System.arraycopy(arr, 0, result, 0, 2);
result[2] = 99;
System.arraycopy(arr, 2, result, 3, 3);
// result = {1, 2, 99, 3, 4, 5}
→ 매번 복잡한 로직 작성.
→ ArrayList 는 add(i, e) 한 줄.
String[] names = new String[10];
names[0] = "Alice";
names[1] = "Bob";
names.length; // 10 (배열 크기)
// 실제 사용 중인 요소 수 (= 2) 는 직접 추적 필요
→ 자체 카운터를 직접 관리해야 함.
→ ArrayList 는 size() 메서드 제공.
// 배열로 다른 자료구조 만들기 = 매우 복잡
// Stack 시뮬레이션
class IntStack {
int[] data = new int[100];
int top = -1;
void push(int v) { data[++top] = v; }
int pop() { return data[top--]; }
}
// Queue 시뮬레이션 — 더 복잡
// HashMap 시뮬레이션 — 매우 복잡
→ 자료구조마다 직접 구현.
→ 컬렉션 프레임워크는 표준 구현 제공.
int[] arr = {3, 1, 4, 1, 5};
// 정렬
Arrays.sort(arr); // 그나마 Arrays 유틸리티
// 검색
int idx = Arrays.binarySearch(arr, 4);
// 포함 여부
// arr.contains(4); ← ❌ 없음
boolean has = false;
for (int n : arr) {
if (n == 4) { has = true; break; }
}
// 변환
// arr.map(...); ← ❌ 없음
// 필터링
// arr.filter(...); ← ❌ 없음
→ 배열은 자료를 담는 것 외에 거의 안 해줌.
→ 컬렉션은 풍부한 API + Stream 까지.
| 한계 | 배열 | 컬렉션 |
|---|---|---|
| 크기 | 고정 | 가변 |
| 삽입/삭제 | 메서드 없음 | add/remove |
| 사용 요소 수 | 직접 추적 | size() |
| 자료구조 종류 | 1개 | 다양 (List/Set/Map/Queue) |
| API | 빈약 | 풍부 + Stream |
배열의 가장 치명적 한계는?
답:
1. 크기 고정 — 동적 데이터 다루기 어려움
2. API 빈약 — 매번 직접 구현
3. 단일 자료구조 — Map, Set 등 별도 구현 필요
이 3가지가 가장 큰 한계.
→ 컬렉션 프레임워크가 이 모든 것을 해결.
Java 1.0 (1996):
- 배열만 있음
- Vector, Hashtable (제한적)
- 자료구조 부족
Java 1.1 (1997):
- 약간의 개선
- 여전히 배열 중심
Java 1.2 (1998) ★:
- "Collection Framework" 도입
- List, Set, Map 인터페이스 등장
- ArrayList, LinkedList, HashMap, HashSet 등 구현체
- Collections 유틸리티 클래스
Java 5 (2004):
- Generic 추가
- List<String> 같은 타입 안전성
Java 8 (2014):
- Stream API
- forEach, removeIf 등 default 메서드
Java 9+:
- List.of(), Map.of() (불변 컬렉션)
- 다양한 개선
→ Java 1.2 가 컬렉션의 현대적 시작.
Java 컬렉션 프레임워크 설계자 (Joshua Bloch) 의 목표:
1. 통일된 인터페이스
→ List, Set 등 공통 동작 추상화
→ 코드 일관성
2. 다양한 구현체
→ 같은 인터페이스에 여러 구현 (ArrayList vs LinkedList)
→ 성능 트레이드오프 선택
3. 알고리즘 표준화
→ Collections.sort(), Collections.shuffle()
→ 자료구조 무관 적용
4. 타입 안전성 (Java 5+)
→ 제네릭으로 컴파일 타임 검증
→ 런타임 ClassCastException 방지
5. 확장성
→ 사용자 정의 자료구조도 통합 가능
→ AbstractList 등 추상 클래스 제공
박승제씨가 ILIC 에서:
// 컬렉션 프레임워크 없으면:
public class ShipmentManager {
Shipment[] shipments = new Shipment[1000];
int size = 0;
void add(Shipment s) {
if (size >= shipments.length) {
Shipment[] bigger = new Shipment[shipments.length * 2];
System.arraycopy(shipments, 0, bigger, 0, size);
shipments = bigger;
}
shipments[size++] = s;
}
// remove, contains, sort, ... 모두 직접 구현
}
// 컬렉션 프레임워크 사용:
public class ShipmentManager {
private final List<Shipment> shipments = new ArrayList<>();
// add, remove, contains 모두 사용 가능
}
→ 코드량 1/10, 버그 가능성 1/100.
→ 박승제씨가 비즈니스 로직에 집중 가능.
자바 컬렉션이 제공하는 추상화:
1. Iterable — "순회 가능"
2. Collection — "요소 모음"
3. List — "순서 있는 모음"
4. Set — "중복 없는 모음"
5. Queue — "선입선출"
6. Deque — "양방향 큐"
7. Map — "키-값 매핑"
각각이 인터페이스 로 정의 → 다양한 구현체 가능.
Java 컬렉션 프레임워크의 등장 이유와 가치는?
답:
1. 배열의 5가지 한계 극복
2. 통일된 인터페이스 로 코드 일관성
3. 다양한 구현체 로 성능 트레이드오프 선택
4. 알고리즘 표준화 (sort, shuffle 등)
5. 타입 안전성 (제네릭, Java 5+)
6. 확장성 (사용자 정의 자료구조)
→ 자바 개발자의 생산성 혁명.
Iterable<E> (java.lang)
│
▼
Collection<E> (java.util)
│
├── List<E>
│ ├── ArrayList<E>
│ ├── LinkedList<E>
│ ├── Vector<E> ★ (Java 1.0, legacy)
│ │ └── Stack<E> ★ (legacy)
│ ├── CopyOnWriteArrayList<E>
│ └── ImmutableList (Java 9+: List.of())
│
├── Set<E>
│ ├── HashSet<E>
│ │ └── LinkedHashSet<E>
│ ├── TreeSet<E> (NavigableSet, SortedSet 구현)
│ ├── EnumSet<E>
│ └── ConcurrentSkipListSet<E>
│
└── Queue<E>
├── LinkedList<E> (List 와 Queue 둘 다 구현)
├── PriorityQueue<E>
├── ConcurrentLinkedQueue<E>
├── BlockingQueue<E> (interface)
│ ├── ArrayBlockingQueue<E>
│ └── LinkedBlockingQueue<E>
└── Deque<E> (interface)
├── ArrayDeque<E>
├── LinkedList<E> (또 등장)
└── ConcurrentLinkedDeque<E>
Map<K, V> (java.util) ★ Collection 아님!
│
├── HashMap<K, V>
│ └── LinkedHashMap<K, V>
├── TreeMap<K, V> (NavigableMap, SortedMap 구현)
├── Hashtable<K, V> ★ (Java 1.0, legacy)
│ └── Properties ★ (legacy)
├── ConcurrentHashMap<K, V>
├── ConcurrentSkipListMap<K, V>
├── EnumMap<K, V>
├── WeakHashMap<K, V>
├── IdentityHashMap<K, V>
└── ImmutableMap (Java 9+: Map.of())
Iterable<E>
└── Collection<E>
├── List<E>
├── Set<E>
│ └── SortedSet<E>
│ └── NavigableSet<E>
└── Queue<E>
└── Deque<E>
Map<K, V>
└── SortedMap<K, V>
└── NavigableMap<K, V>
→ 인터페이스의 계층 이 핵심.
→ 구현체는 다양하지만 인터페이스로 추상화.
// 변수 선언 시 인터페이스 우선
List<Shipment> shipments = new ArrayList<>(); // 구현체 ArrayList
Set<String> codes = new HashSet<>(); // 구현체 HashSet
Map<Long, Cargo> cargoes = new HashMap<>(); // 구현체 HashMap
// 메서드 매개변수도 인터페이스
public void process(List<Shipment> shipments) { ... }
public Set<String> extractCodes(Collection<Shipment> input) { ... }
이유:
new ArrayList<>() → new LinkedList<>())박승제씨의 ILIC 코드에서:
@Service
public class ShipmentService {
private final List<ShipmentListener> listeners = new ArrayList<>();
private final Map<Long, Shipment> cache = new ConcurrentHashMap<>();
private final Set<String> processedBlNos = ConcurrentHashMap.newKeySet();
public List<Shipment> findByStatus(ShipmentStatus status) {
return repository.findByStatus(status);
// 반환 타입은 List<Shipment> (인터페이스)
// 실제 구현체는 ArrayList (JPA 가 반환)
}
}
→ 인터페이스 의존 + 구현체 자유.
자바 컬렉션 계층의 최상위 인터페이스는?
답:
Iterable<E> (java.lang 패키지)Collection<E> (java.util)List, Set, QueueMap 은 별도 계층 (Collection 자식이 아님)→ Iterable 을 구현하면 for-each 사용 가능.
박승제씨가 2주차 Unit 5.1 에서도 본 핵심 질문:
"왜 Map<K,V> 는 Collection<E> 를 상속하지 않는가?"
Collection<E>:
- 단일 요소 (E) 들의 모음
- 각 요소가 자기 자체로 의미 있음
- 예: List<Shipment> — Shipment 들의 모음
Map<K, V>:
- 키 (K) 와 값 (V) 의 매핑
- 키-값 쌍 (Entry) 이 단위
- 예: Map<Long, Shipment> — id 로 Shipment 조회
공통 인터페이스가 어색한 이유:
- Collection 의 add(E e) 가 Map 에선 의미 없음
- Map 의 put(K, V) 는 Collection 에 없음
- iterator() 가 Collection 은 E, Map 은 무엇?
→ 단일 요소 vs 키-값 쌍 의 차이.
Collection 의 본질:
S = {e₁, e₂, e₃, ...} ← 집합 / 시퀀스
Map 의 본질:
f: K → V ← 함수 (사상)
f(k₁) = v₁, f(k₂) = v₂
수학적으로:
→ 서로 다른 추상화.
만약 Map 이 Collection 을 상속했다면:
// 가상의 잘못된 설계
Map<Long, Shipment> map = ...;
map.add(???); // 무엇을? 키만? 값만? 둘 다?
map.contains(???); // 키? 값? 엔트리?
map.remove(???); // 모호
→ 모든 메서드가 모호해짐.
→ 분리가 명확한 설계.
Map 은 Collection 자식이 아니지만, 세 가지 view 제공:
Map<Long, Shipment> map = ...;
// 1. 키들의 집합 (Set)
Set<Long> keys = map.keySet();
// 2. 값들의 컬렉션 (Collection, 중복 가능)
Collection<Shipment> values = map.values();
// 3. 엔트리들의 집합 (Set<Map.Entry>)
Set<Map.Entry<Long, Shipment>> entries = map.entrySet();
→ View 를 통해 Collection 처럼 다룰 수 있음.
→ for-each 도 가능:
for (Map.Entry<Long, Shipment> entry : map.entrySet()) {
// ...
}
java.util 의 다른 분리:
- Iterator (순회 도구) vs Iterable (순회 가능)
- Comparator (외부 비교) vs Comparable (자기 비교)
- Function vs BiFunction
같은 패턴:
- "비슷해 보이지만 본질이 다르면 분리"
→ 자바 설계의 일관된 철학.
// 박승제씨가 메서드 시그니처 만들 때:
// ❌ Collection 으로 통일 시도
public void process(Collection<?> data) { ... } // 모호
// ✓ List 와 Map 분리
public void processShipments(List<Shipment> list) { ... }
public void processIndexed(Map<Long, Shipment> map) { ... }
// ✓ 또는 view 활용
public void processKeys(Set<Long> ids) { ... }
public void processValues(Collection<Shipment> shipments) { ... }
→ 각 자료구조의 특성에 맞는 시그니처.
Map 이 Collection 의 자식이 아닌 이유 3가지는?
답:
1. 본질적 차이: 단일 요소 vs 키-값 쌍
2. 수학적 다른 추상화: 집합 vs 함수
3. API 의 모순 회피: add, contains 등이 Map 에선 모호
→ 그러나 view (keySet, values, entrySet) 로 Collection 처럼 다룰 수 있음.
| 자료구조 | 핵심 의미 | 순서 | 중복 | 인덱스 |
|---|---|---|---|---|
| List | "순서 있는 리스트" | ✓ 삽입 순서 | ✓ 허용 | ✓ |
| Set | "중복 없는 집합" | 보통 X (LinkedHashSet 예외) | ❌ | ❌ |
| Queue | "처리 대기 줄" | 처리 순서 | ✓ 허용 | ❌ |
| Deque | "양방향 큐" | 양 끝 | ✓ | ❌ |
→ 의도 에 따라 선택.
List<Shipment> shipments = new ArrayList<>();
// 삽입 순서 보존
shipments.add(shipment1);
shipments.add(shipment2);
shipments.add(shipment3);
// 인덱스 접근
shipments.get(0); // shipment1
shipments.get(2); // shipment3
// 중복 허용
shipments.add(shipment1); // 또 추가 가능
언제 List 를 쓰나:
Set<String> uniqueBlNos = new HashSet<>();
uniqueBlNos.add("BL-001");
uniqueBlNos.add("BL-002");
uniqueBlNos.add("BL-001"); // 중복 → 무시됨
uniqueBlNos.size(); // 2
// 포함 여부 빠름 (해시 기반 O(1))
uniqueBlNos.contains("BL-001"); // true
언제 Set 을 쓰나:
Queue<Task> taskQueue = new LinkedList<>();
// 또는 ArrayDeque 권장 (2주차 Phase 5)
taskQueue.offer(task1); // 끝에 추가
taskQueue.offer(task2);
taskQueue.offer(task3);
Task t = taskQueue.poll(); // 앞에서 꺼냄 → task1
Task t2 = taskQueue.poll(); // → task2
// 또는 peek (꺼내지 않고 보기)
taskQueue.peek(); // task3 (꺼내지 않음)
언제 Queue 를 쓰나:
Map<Long, Shipment> shipmentById = new HashMap<>();
shipmentById.put(1L, shipment1);
shipmentById.put(2L, shipment2);
Shipment s = shipmentById.get(1L); // O(1) 조회
언제 Map 을 쓰나:
박승제씨의 자료구조 선택 트리:
1. 키로 값 찾는가?
YES → Map
NO → 2
2. 중복 허용?
NO → Set
YES → 3
3. FIFO 처리?
YES → Queue
NO → List
@Service
public class ShipmentService {
// List — 순서 있는 운송 목록
private final List<Shipment> recentShipments = new ArrayList<>();
// Set — 중복 없는 처리된 ID
private final Set<Long> processedIds = new HashSet<>();
// Map — ID로 빠른 조회 캐시
private final Map<Long, Shipment> cache = new HashMap<>();
// Queue — 처리 대기 큐
private final Queue<ShipmentEvent> eventQueue = new ConcurrentLinkedQueue<>();
}
→ 같은 데이터를 다양한 자료구조로 다룸.
"중복 없는 운송장 번호 목록을 빠르게 검색" 하려면 어떤 자료구조?
답:
만약 정렬도 필요하면:
만약 삽입 순서도 유지하면:
→ 같은 "중복 없는" 요구도 추가 조건에 따라 다른 선택.
박승제씨의 결정 프로세스:
Step 1: 데이터의 본질 파악
- 키-값 매핑? → Map
- 순서 있는 시퀀스? → List
- 중복 없는 집합? → Set
- 처리 대기? → Queue
Step 2: 추가 요구사항 확인
- 정렬 필요? → TreeXxx
- 삽입 순서 유지? → LinkedXxx
- 멀티스레드 안전? → ConcurrentXxx
- 불변? → List.of(), Set.of(), Map.of()
Step 3: 성능 트레이드오프 검토
- 빈번 조회? → HashMap, HashSet
- 빈번 삽입/삭제? → LinkedList (단, Phase 5 에서 본 캐시 효율도)
- 인덱스 접근? → ArrayList
Step 4: 구현체 선택
- List → ArrayList (기본), LinkedList (양쪽 작업), CopyOnWriteArrayList (멀티스레드)
- Set → HashSet (기본), TreeSet (정렬), LinkedHashSet (순서 유지)
- Map → HashMap (기본), TreeMap (정렬), ConcurrentHashMap (멀티스레드)
- Queue → ArrayDeque (기본), PriorityQueue (우선순위), BlockingQueue (멀티스레드)
박승제씨가 ILIC 코드에서 90% 사용:
- ArrayList<E>
- HashMap<K, V>
- HashSet<E>
9% 사용:
- ArrayDeque<E> (큐/스택 필요 시)
- LinkedHashMap (LRU 캐시 등)
- ConcurrentHashMap (멀티스레드)
- TreeMap (정렬 키 필요)
1% 사용:
- LinkedList (드물게)
- Vector, Hashtable (절대 안 씀, legacy)
- 특수 컬렉션 (EnumMap, WeakHashMap 등)
→ 박승제씨가 매일 결정하는 90:9:1.
// JPA Repository 반환
List<Shipment> shipments = repository.findAll();
// 내부 구현은 ArrayList
// 인터페이스는 List
// 단일 스레드 캐시
private final Map<Long, Shipment> cache = new HashMap<>();
// 멀티스레드 캐시
private final Map<Long, Shipment> sharedCache = new ConcurrentHashMap<>();
// 더 진보된 캐시
private final Cache<Long, Shipment> caffeineCache =
Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofHours(1))
.build();
// 단일 스레드 이벤트 큐
private final Queue<ShipmentEvent> eventQueue = new ArrayDeque<>();
// 멀티스레드 안전 이벤트 큐
private final Queue<ShipmentEvent> sharedQueue = new ConcurrentLinkedQueue<>();
// 블로킹 큐 (소비자가 대기)
private final BlockingQueue<ShipmentEvent> blockingQueue =
new LinkedBlockingQueue<>(1000);
public class User {
private Set<Permission> permissions; // 중복 없는 권한
public boolean hasPermission(Permission p) {
return permissions.contains(p); // O(1)
}
}
public class BatchProcessor {
private final Set<Long> processedIds = new HashSet<>();
public void process(Shipment s) {
if (processedIds.contains(s.getId())) {
return; // 이미 처리됨
}
// ... 처리
processedIds.add(s.getId());
}
}
Map<String, List<Shipment>> byRoute = shipments.stream()
.collect(Collectors.groupingBy(Shipment::getRoute));
// 결과: 노선별로 그룹 지어진 Shipment 들
// 시간순 이벤트
TreeMap<LocalDateTime, ShipmentEvent> timeline = new TreeMap<>();
timeline.put(t1, event1);
timeline.put(t2, event2);
// 시간순으로 순회
timeline.forEach((time, event) -> { ... });
// 특정 시간 범위
SortedMap<LocalDateTime, ShipmentEvent> recent =
timeline.tailMap(LocalDateTime.now().minusDays(1));
박승제씨의 컬렉션 사용 검토:
변수 선언:
- 인터페이스 타입으로 선언?
- 적절한 구현체 선택?
- 초기 크기 지정 (큰 컬렉션)?
사용 패턴:
- List 인데 contains 빈번? → Set 으로 변환?
- List 인데 앞쪽 삽입? → Deque 검토?
- Map 인데 정렬 필요? → TreeMap?
- 멀티스레드 환경? → Concurrent 컬렉션?
메서드 시그니처:
- 매개변수가 인터페이스?
- 반환 타입이 인터페이스?
- 불변 컬렉션 반환 (List.copyOf)?
성능:
- 빈번 조회면 HashSet/HashMap?
- 정렬 필요 시 적절한 자료구조?
- 메모리 사용 적절?
ILIC 에서 가장 자주 쓰는 자료구조 3가지는?
답:
1. List — 도메인 객체 모음 (ArrayList)
2. Map<K, V> — 키로 조회 (HashMap, ConcurrentHashMap)
3. Set — 중복 제거, 빠른 검색 (HashSet)
→ 90% 가 이 3가지로 해결.
// ❌ Entity 가 배열을 노출
public class Shipment {
private Cargo[] cargoes; // 배열
public Cargo[] getCargoes() {
return cargoes; // 외부에서 변경 가능
}
}
// ✓ 컬렉션 + 방어적 복사
public class Shipment {
private final List<Cargo> cargoes = new ArrayList<>();
public List<Cargo> getCargoes() {
return List.copyOf(cargoes); // 불변
}
}
// ❌ 구현체로 선언
ArrayList<Shipment> shipments = new ArrayList<>();
// ✓ 인터페이스로 선언
List<Shipment> shipments = new ArrayList<>();
이유:
// ❌ Vector 사용
Vector<Shipment> shipments = new Vector<>();
문제:
// ✓ 멀티스레드 환경
List<Shipment> shipments = Collections.synchronizedList(new ArrayList<>());
// ✓ 또는 더 권장
List<Shipment> shipments = new CopyOnWriteArrayList<>(); // 읽기 위주
// ✓ 또는
List<Shipment> shipments = new ArrayList<>(); // ReadWriteLock 으로 직접
// ❌ Stack (Legacy)
Stack<Frame> stack = new Stack<>();
stack.push(frame);
Frame f = stack.pop();
문제:
// ✓ ArrayDeque
Deque<Frame> stack = new ArrayDeque<>();
stack.push(frame);
Frame f = stack.pop();
// ❌ Hashtable (Legacy)
Hashtable<Long, Shipment> map = new Hashtable<>();
문제:
// ✓ HashMap (단일 스레드)
Map<Long, Shipment> map = new HashMap<>();
// ✓ ConcurrentHashMap (멀티 스레드)
ConcurrentMap<Long, Shipment> map = new ConcurrentHashMap<>();
String[] arr = {"A", "B", "C"};
// ❌ 함정
List<String> list = Arrays.asList(arr);
list.add("D"); // UnsupportedOperationException!
Arrays.asList():
// ✓ 가변 List 로 변환
List<String> list = new ArrayList<>(Arrays.asList(arr));
// ✓ 또는 Stream
List<String> list = Arrays.stream(arr).collect(Collectors.toList());
// ✓ 또는 Java 9+
List<String> immutable = List.of("A", "B", "C");
Map<Long, Shipment> cache = new HashMap<>();
// ❌ null 체크 누락
Shipment s = cache.get(id);
s.getBlNo(); // NPE 가능!
// ✓ null 체크
Shipment s = cache.get(id);
if (s != null) {
s.getBlNo();
}
// ✓ Optional
Optional.ofNullable(cache.get(id))
.ifPresent(shipment -> ...);
// ✓ getOrDefault
Shipment s = cache.getOrDefault(id, defaultShipment);
// ❌ ConcurrentModificationException
List<Shipment> list = new ArrayList<>();
// ... 데이터
for (Shipment s : list) {
if (s.isExpired()) {
list.remove(s); // ❌ CME
}
}
// ✓ Iterator
Iterator<Shipment> it = list.iterator();
while (it.hasNext()) {
Shipment s = it.next();
if (s.isExpired()) {
it.remove();
}
}
// ✓ Java 8+ removeIf
list.removeIf(Shipment::isExpired);
박승제씨가 2주차 Unit 6.2 에서 본 패턴.
박승제씨가 ILIC 코드에서 피할 것:
레거시:
- Vector → ArrayList 또는 CopyOnWriteArrayList
- Hashtable → HashMap 또는 ConcurrentHashMap
- Stack → ArrayDeque
배열 노출:
- Entity 가 배열 반환 → 컬렉션
- 매개변수가 배열 → 컬렉션
타입 선언:
- 구현체로 변수 선언 → 인터페이스
- 구현체로 메서드 시그니처 → 인터페이스
변환 함정:
- Arrays.asList 후 add → new ArrayList<>()
- List.of 변경 시도 → 불변 의식
순회 + 삭제:
- for-each 안 list.remove → Iterator 또는 removeIf
| Q | 핵심 답변 |
|---|---|
| 배열과 컬렉션 차이? | 고정 vs 가변, 단일 자료구조 vs 다양 |
| Java 컬렉션 프레임워크 등장? | Java 1.2 (1998), Joshua Bloch |
| Map 이 Collection 자식 아닌 이유? | 단일 요소 vs 키-값 쌍 본질 차이 |
| List/Set/Queue 차이? | 순서/중복/처리 의도 |
| Vector 안 쓰는 이유? | Legacy, synchronized 비용 |
| Stack 클래스 안 쓰는 이유? | Legacy, ArrayDeque 권장 |
| Iterable 의 역할? | for-each 가능 |
| Arrays.asList 함정? | 고정 크기, add 불가 |
| 인터페이스로 변수 선언? | 구현체 교체 자유, 유연성 |
| ConcurrentModificationException? | 순회 중 외부 변경 시. Iterator.remove |
1. 컬렉션 = 배열의 한계를 메우는 프레임워크
2. 계층 구조
3. ILIC 표준
이번 Unit에서 컬렉션 전체 지도를 봤다면, 다음은 Set 인터페이스의 3가지 구현체.
🚀 Phase 2 — 컬렉션 프레임워크 전체 지도
✅ Unit 2.1 배열의 한계와 컬렉션의 등장 ← 여기
⏭ Unit 2.2 Set 3형제 (HashSet/TreeSet/LinkedHashSet)
⏭ Unit 2.3 List 3형제 (ArrayList/LinkedList/Vector)
⏭ Unit 2.4 Queue (FIFO 자료구조)
⏭ Unit 2.5 Map 5형제
⏭ Unit 2.6 컬렉션 전체 지도 정리
✅ Phase 1 — Pass by Value (1.1 ~ 1.3 완주)
🚀 Phase 2 — 컬렉션 프레임워크 (1/6 진행)
⏭ Phase 3-10
총: 4/43 Unit 작성 (약 9%)