자바를 공부하다 보면 꼭 만나게 되는 개념 중 하나가 바로 컬렉션 프레임워크이다.
처음에는 왜 이렇게 복잡한 구조를 써야 하지? 그냥 배열 쓰면 안 되나? 라는 생각도 들었는데,
실제로 프로젝트를 하다 보면 다양한 데이터를 유연하고 효율적으로 다루는 구조가 정말 필요하다는 걸 체감하곤 한다.
이번 글에서는 내가 Java의 정석 책 을 기반으로 공부하면서 정리한 컬렉션 프레임워크의 개념과 특징들을 공유하려 한다.
단순 암기보다는, 왜 이렇게 설계됐는지 고민하면서 정리해봤다.
컬렉션 프레임워크?
컬렉션 프레임워크는 말 그대로 여러 데이터(객체)를 효과적으로 저장하고 처리하기 위한 표준화된 구조이다.
자바에서는 이런 구조를 클래스와 인터페이스의 조합으로 제공한다.
🤔 컬렉션? 프레임워크?
- 컬렉션: 데이터의 집합을 저장하고 관리하는 객체를 뜻한다.
- 프레임워크: 단순 기능만 제공하는 것이 아니라, 개발 방식까지 정형화해서 재사용성과 생산성을 높여준다.
3대 컬렉션 인터페이스
컬렉션은 크게 List, Set, Map 세 가지로 나뉜다. 처음엔 이 세 가지의 차이가 좀 헷갈렸는데, 직접 써보면서 아래처럼 정리했다:
타입 | 순서 | 중복 | 구조 |
List | O | O | 배열 느낌 (순서 있음) |
Set | X | X | 집합 (중복X) |
Map | X (Key 기준) | Key: X / Value: O | 딕셔너리 구조 (Key-Value 쌍) |
List: 순서가 중요할 때
대표적인 구현체로는 ArrayList, LinkedList, Vector(→ Stack)이 있다.
ArrayList
- 내부적으로 배열을 사용
- 순차적 추가/삭제는 빠름
- 하지만 중간 삽입/삭제는 느림 (데이터 이동 필요)
- 배열 크기 제한 있음 → 재할당 필요
LinkedList
- 연결 리스트 기반 구조
- 중간 삽입/삭제는 빠름
- 하지만 임의 접근(인덱스 접근)은 느림 (처음부터 타고 가야 함)
처음에는 무조건 ArrayList만 써도 될 줄 알았는데,
데이터 삽입/삭제가 빈번한 경우엔 LinkedList가 유리하기에 상황에 맞게 사용해야 겠다.
Set: 중복 없는 데이터 저장
HashSet, TreeSet, LinkedHashSet 등이 있다.
HashSet
- 중복 X, 순서 없음
- equals()와 hashCode()를 반드시 오버라이딩해야 제대로 작동한다
Set<String> names = new HashSet<>();
names.add("Alice");
names.add("Alice"); // 중복 → 저장되지 않음
TreeSet
- 정렬된 상태로 저장
- 내부 구조는 레드-블랙 트리 기반
- Comparable을 구현하거나 Comparator를 넘겨줘야 함
TreeSet에 객체를 저장할 때 에러가 나서 한참 헤맸는데,
알고 보니 정렬 기준을 알려주지 않아서 생긴 문제였다.
그렇기에 Tree Set은 객체 비교 방법을 명확히 지정해줘야 한다.
Map: 키-값 쌍으로 데이터 관리
HashMap, Hashtable, TreeMap, LinkedHashMap 등이 있다.
HashMap
- null 키, null 값 허용
- 키는 중복 X, 값은 중복 O
Map<String, Integer> scores = new HashMap<>();
scores.put("John", 90);
scores.put("John", 95); // 키 중복 → 값이 덮어쓰기됨
TreeMap
- 키를 기준으로 자동 정렬됨
- Comparator 혹은 Comparable 필수
Hashtable이 null을 허용하지 않는다는 걸 몰라서 처음에 NullPointerException을 맞은 적이 있다.
이젠 절대 안 까먹을것 같다..
Iterator & ListIterator
컬렉션의 요소를 순회하려면 Iterator를 쓴다. hasNext(), next()로 순차 접근이 가능하고, remove()로 요소 삭제도 할 수 있다.
ListIterator
- Iterator보다 기능 확장
- 양방향 탐색 가능 (next, previous)
- List 구현체에서만 사용 가능
for-each 문으로 순회하면 삭제가 안 되는데, Iterator는 삭제가 가능해서 유용하다.
특히 Set을 순회하며 조건에 맞는 요소를 삭제할 때 필요하다.
Comparable vs Comparator
정렬 기준을 지정하는 두 가지 방식이다.
- Comparable: 객체 내부에서 기본 정렬 기준을 정의 (compareTo)
- Comparator: 외부에서 정렬 기준을 커스터마이징 (compare)
클래스에 기본 정렬 기준이 있다면 Comparable,
그렇지 않고 상황에 따라 다르게 정렬하고 싶을 땐 Comparator
이걸 헷갈리지 않게 구분하는 게 중요하다.
Collections 유틸리티 클래스
자바는 Collections라는 유틸리티 클래스를 통해 다양한 기능을 제공한다.
1. 동기화 처리
멀티 스레드 환경에서 컬렉션을 안전하게 쓰려면 동기화가 필요하다.
List list = Collections.synchronizedList(new ArrayList<>());
2. 읽기 전용(불변 컬렉션) 만들기
List<String> unmodifiable = Collections.unmodifiableList(list);
3. 싱글톤 컬렉션
List<String> singleton = Collections.singletonList("onlyOne");
4. 타입 제한
List rawList = new ArrayList();
List<String> checkedList = Collections.checkedList(rawList, String.class);
컬렉션을 다루다 보면 실수로 잘못된 타입을 넣거나, 의도치 않게 값이 바뀌는 일이 생길 수 있다.
이런 유틸 메서드를 잘 활용하면 안정성이 높아진다는 걸 배웠다.
마무리
이번에 컬렉션 프레임워크를 공부하면서 정말 많이 느낀 건, 자료구조에 대한 이해가 결국 코드의 효율성을 결정한다는 것이다.
처음에는 단순히 외워서 사용했는데, 이제는 상황에 맞게 어떤 구조를 선택해야 할지 고민하는 습관이 생겼다.
앞으로는 각 컬렉션의 성능 특성까지 고려해서, 더 효율적인 코드 작성을 목표로 해보려 한다.
그리고 실제 프로젝트에서 어떤 구조가 더 잘 맞는지 비교하면서 실전 감각도 쌓아가고 싶다.
'자바' 카테고리의 다른 글
자바에서 쓰레드 사용법과 동기화 (0) | 2025.05.08 |
---|---|
자바 제네릭스, 애너테이션, 그리고 enum (4) | 2025.05.05 |
자바 예외 처리 (3) | 2025.04.25 |
자바 객체지향 이해하기 (0) | 2025.04.17 |
자바 문법 - static, 오버로딩, 생성자, 초기화 (1) | 2025.04.14 |