Java프로그래밍 11강 - 컬렉션
여러 원소를 하나의 그룹으로 저장하고 관리하는 Java 컬렉션과 JCF의 인터페이스 계층을 학습한다. HashSet, ArrayList, LinkedList, HashMap의 특징과 주요 메소드, 반복자를 이용한 탐색 및 복합 자료 구성 방법을 예제와 함께 정리한다.
1. 컬렉션과 JCF
컬렉션의 의미
컬렉션(collection)은 여러 원소를 하나의 그룹으로 묶어 관리하기 위한 자료구조이다. 프로그램에서는 원소를 저장하는 것뿐 아니라 추가·삭제·탐색·정렬·수정하는 작업도 반복적으로 필요하다. 자료를 배열만으로 직접 관리하면 각 작업을 별도로 구현해야 하지만, 컬렉션을 사용하면 목적에 맞는 표준 자료구조와 메소드를 이용할 수 있다.
JCF의 역할
JCF(Java Collections Framework)는 컬렉션, 즉 데이터 목록을 표현하고 다루기 위한 인터페이스와 클래스의 모음이다. 클래스와 인터페이스를 집합으로 제공하여 여러 컬렉션을 일관된 방식으로 사용할 수 있게 한다.
- 표준화된 인터페이스가 컬렉션에 필요한 공통 기능을 표현한다.
- 구현 클래스가 배열, 연결 리스트, 해싱, 트리 등 실제 저장 방식을 제공한다.
- 저장 방식이 달라도 공통 인터페이스를 통해 일관성 있게 다룰 수 있다.
- 자료의 저장, 정렬, 검색, 수정에 필요한 도구를 재사용할 수 있다.
JCF는 하나의 자료구조가 아니라 컬렉션을 표현하는 인터페이스와 그 인터페이스를 구현한 클래스들의 통합 체계이다.
2. JCF의 주요 인터페이스와 계층
Set, List, Queue, Map
| 인터페이스 | 핵심 특징 | 대표 구현 클래스 |
|---|---|---|
Set | 순서의 의미가 없고 중복 원소를 허용하지 않음 | HashSet, LinkedHashSet, TreeSet |
List | 입력 순서를 유지하고 중복을 허용하며 인덱스로 접근 | ArrayList, Vector, Stack, LinkedList |
Queue | 원소의 삽입과 삭제가 FIFO 방식 | LinkedList |
Map | 키와 값의 쌍으로 저장하며 키는 중복될 수 없음 | HashMap, LinkedHashMap, TreeMap |
계층 관계
Set, List, Queue는 공통 기능을 정의한 Collection 인터페이스의 하위 인터페이스이고, Collection은 반복 가능한 객체를 나타내는 Iterable과 연결된다. SortedSet은 정렬된 집합, Deque는 양쪽 끝을 사용할 수 있는 큐 기능을 확장한다.
Map은 키와 값의 쌍을 다루므로 Collection 계층과 별도로 구성된다. 정렬된 키를 다루는 SortedMap이 있으며, 대표적인 트리 구현은 TreeMap이다.
시험에서는 Map이 Collection의 하위 인터페이스가 아니라 별도 계층이라는 점과 LinkedList가 List와 Queue의 기능을 함께 제공한다는 점을 구분한다.
3. 컬렉션 객체 선언과 제네릭 타입
JCF의 인터페이스와 클래스는 java.util 패키지에 포함되며 제네릭 타입으로 정의되어 있다. 컬렉션이 다룰 원소나 키·값의 자료형을 타입 인자로 지정하면 컴파일러가 잘못된 자료형의 저장을 검사할 수 있다.
Set<Integer> set = new HashSet<>();
List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();
Queue<Integer> queue = new LinkedList<>();
Map<String, Integer> map = new HashMap<>();
변수는 구체적인 구현 클래스가 아니라 인터페이스 타입으로 선언하는 경우가 많다. 이렇게 하면 필요한 기능이 같은 범위 안에서 구현 클래스를 바꾸기 쉽다. 예를 들어 목록의 선언을 List<Integer>로 유지하면서 생성 객체만 ArrayList에서 LinkedList로 바꿀 수 있다.
컬렉션을 선언할 때는 먼저 중복, 순서, 인덱스, FIFO, 키-값 중 필요한 성질을 판단하고 그에 맞는 인터페이스와 구현 클래스를 선택한다.
4. Collection 인터페이스의 공통 메소드
삽입과 삭제
| 메소드 | 기능 |
|---|---|
boolean add(E e) | 원소 하나를 추가하고 성공하면 true 반환 |
boolean addAll(Collection<? extends E> c) | 주어진 컬렉션의 모든 원소를 현재 컬렉션에 추가 |
boolean remove(Object o) | 주어진 원소를 현재 컬렉션에서 제거 |
boolean removeAll(Collection<?> c) | 주어진 컬렉션에 포함된 모든 원소를 현재 컬렉션에서 제거 |
boolean retainAll(Collection<?> c) | 현재 컬렉션에서 주어진 컬렉션에도 있는 원소만 남김 |
void clear() | 모든 원소를 제거하여 컬렉션을 비움 |
탐색과 상태 확인
| 메소드 | 기능 |
|---|---|
boolean contains(Object o) | 지정한 원소가 있으면 true |
boolean containsAll(Collection<?> c) | 주어진 컬렉션의 모든 원소를 포함하면 true |
boolean isEmpty() | 원소가 하나도 없으면 true |
int size() | 포함된 원소의 개수 반환 |
Object[] toArray() | 현재 원소를 객체 배열로 변환 |
Iterator<E> iterator() | 원소를 순회할 반복자 반환 |
hashCode()는 컬렉션의 해시 코드값을 반환하고, equals(Object)는 두 컬렉션의 동등성을 비교할 때 사용한다. 공통 인터페이스 덕분에 HashSet, ArrayList, LinkedList에서 같은 이름의 기본 연산을 사용할 수 있다.
5. HashSet: 중복 없는 집합
HashSet은 Set 인터페이스를 구현한 해시 기반 컬렉션이다. 원소의 입력 순서를 보장하지 않고 같은 원소를 중복해서 저장하지 않는다. 집합에 이미 있는 원소를 다시 add()하면 새 원소가 추가되지 않고 false가 반환된다.
Set<String> set = new HashSet<>();
set.add("one");
set.add("two");
set.add("three");
set.add("four");
System.out.println(set.add(new String("one"))); // false
System.out.println(set.size()); // 4
System.out.println(set.contains("four")); // true
new String("one")은 기존 문자열 객체와 다른 객체일 수 있지만 문자열의 내용이 같으므로 집합에서는 같은 원소로 판단된다. remove()로 원소를 삭제한 뒤 clear()를 호출하면 모든 원소가 사라지고 크기는 0이 된다.
HashSet은 중복 제거와 포함 여부 검사가 중요하고, 입력 순서나 인덱스 접근이 필요하지 않은 자료에 적합하다.
6. ArrayList: 순서와 인덱스를 가진 목록
ArrayList의 특징
ArrayList<E>는 List 인터페이스를 구현하며 크기를 조절할 수 있는 배열 방식의 컬렉션이다. 같은 자료가 여러 번 저장될 수 있고 입력 순서대로 관리된다. 특정 위치의 원소를 참조하거나 삽입·수정·삭제할 때 인덱스를 사용할 수 있다.
주요 메소드
| 메소드 | 기능 또는 반환값 |
|---|---|
boolean add(E e) | 목록의 맨 뒤에 원소 추가 |
void add(int index, E element) | 지정 위치에 원소 삽입 |
boolean remove(Object o) | 같은 원소를 찾아 삭제하고 성공 여부 반환 |
E remove(int index) | 지정 위치의 원소를 삭제하고 삭제된 원소 반환 |
E get(int index) | 지정 위치의 원소 반환 |
E set(int index, E element) | 지정 위치를 새 원소로 바꾸고 기존 원소 반환 |
int indexOf(Object o) | 처음 나타나는 위치 반환 |
int lastIndexOf(Object o) | 마지막으로 나타나는 위치 반환 |
List<E> subList(int from, int to) | 지정 범위의 부분 목록 반환 |
listIterator()와 listIterator(index)는 목록을 순회하는 ListIterator를 제공한다. 인덱스를 받는 오버로드는 지정 위치에서 반복을 시작할 수 있다.
remove(Object)는 성공 여부인 boolean을 반환하고, remove(int)는 삭제된 원소를 반환한다. 호출 인자의 자료형에 따라 선택되는 메소드가 달라진다.
7. ArrayList 메소드 실행 흐름
List<String> list = new ArrayList<>();
list.add("one");
list.add("two");
list.add("three");
list.add(1, "one");
list.add("five");
위 목록은 차례로 [one, one, two, three, five]가 된다. add(1, "one")이 인덱스 1에 새 원소를 넣으면서 뒤의 원소들이 이동하며, 중복된 one도 그대로 유지된다.
| 호출 | 결과 | 이유 |
|---|---|---|
list.size() | 5 | 원소가 다섯 개 저장됨 |
list.indexOf("one") | 0 | 첫 번째 one의 위치 |
list.get(2) | two | 인덱스는 0부터 시작 |
list.lastIndexOf("one") | 1 | 마지막 one의 위치 |
list.set(3, "four") | three | 기존 원소를 반환하고 four로 교체 |
list.remove(4) | five | 인덱스 4의 원소를 삭제하여 반환 |
list.remove("one") | true | 일치하는 첫 원소 삭제에 성공 |
ArrayList의 위치는 0부터 시작하며, 삽입과 삭제 뒤에는 뒤쪽 원소의 인덱스가 달라질 수 있다.
8. 컬렉션 원소 순회
인덱스와 향상된 for문
List처럼 인덱스를 제공하는 컬렉션은 일반 for문에서 size()와 get()을 이용해 탐색할 수 있다. 인덱스 자체가 필요하지 않으면 향상된 for문으로 원소를 직접 받을 수 있다.
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
for (String value : list) {
System.out.println(value);
}
Iterator 인터페이스
Iterator<E>는 컬렉션에 저장된 원소를 차례대로 다루는 표준 반복자이다. hasNext()로 다음 원소가 있는지 확인하고, next()로 원소를 얻는다. 반복 도중 현재 원소를 제거할 때 사용할 수 있는 remove()도 제공한다.
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
HashSet, ArrayList, LinkedList 등은 iterator()를 통해 같은 방식의 순회를 지원한다. 이 구조는 컬렉션 내부 저장 방식과 관계없이 원소를 순서대로 처리하게 한다.
forEach와 Consumer
forEach()에는 원소 하나를 받아 처리하는 Consumer<E>를 전달한다. 익명 구현 클래스, 람다식 변수, 직접 작성한 람다식 모두 사용할 수 있다.
Consumer<String> printer =
value -> System.out.println(value);
list.forEach(printer);
list.forEach(value -> System.out.println(value));
9. LinkedList와 스택·큐 기능
List 구현체로서의 LinkedList
LinkedList는 ArrayList와 마찬가지로 List 인터페이스를 구현한다. 따라서 앞의 목록 예제에서 변수 타입을 List로 유지하고 생성 객체만 LinkedList로 바꾸어 공통 메소드를 사용할 수 있다.
스택 연산
push(E)는 목록의 앞쪽에 원소를 넣고, pop()은 앞쪽 원소를 삭제하여 반환한다. 이 두 메소드를 이용하면 나중에 넣은 원소를 먼저 꺼내는 스택 형태의 처리가 가능하다.
Queue 인터페이스 구현
LinkedList는 Queue 인터페이스도 구현한다. 큐에서는 원소를 뒤에 넣고 앞에서 꺼내므로 먼저 들어온 원소가 먼저 나오는 FIFO 방식이 된다.
| 연산 | 메소드 | 기능 |
|---|---|---|
| 뒤에 삽입 | offer(E), add(E) | 큐의 뒤쪽에 원소 추가 |
| 앞에서 삭제 | poll(), remove() | 앞 원소를 삭제하고 반환 |
| 앞 원소 읽기 | peek(), element() | 삭제하지 않고 앞 원소 확인 |
LinkedList<String> queue = new LinkedList<>();
queue.offer("one");
queue.offer("two");
queue.offer("three");
queue.offer("four");
String value = queue.poll();
while (value != null) {
System.out.println(value);
value = queue.poll();
}
출력 순서는 one, two, three, four이다. 삽입된 순서대로 poll()되어 FIFO 동작을 확인할 수 있다.
10. Map 인터페이스와 HashMap
키와 값의 매핑
Map<K, V>은 키(key)와 값(value)의 쌍으로 이루어진 원소를 관리하는 인터페이스이다. 키는 중복되지 않으며 하나의 키에는 하나의 값만 대응된다. 같은 키로 값을 다시 저장하면 새 원소가 하나 더 생기는 것이 아니라 기존 값이 바뀐다. 일반적인 Map에서는 원소의 순서가 중요하지 않다.
| 메소드 | 기능 |
|---|---|
V put(K key, V value) | 키와 값의 매핑 추가, 키가 이미 있으면 값 변경 |
V get(Object key) | 키에 대응하는 값 반환, 없으면 null |
V remove(Object key) | 키에 대응하는 매핑 삭제 후 값 반환, 없으면 null |
boolean containsKey(Object key) | 지정 키가 존재하면 true |
Collection<V> values() | 저장된 값들로 이루어진 컬렉션 반환 |
Set<K> keySet() | 저장된 키들로 이루어진 집합 반환 |
HashMap
HashMap은 해싱을 이용하여 Map 인터페이스를 구현한 클래스이다. 목록의 위치를 따라 탐색하는 ArrayList나 LinkedList와 달리 키를 이용해 값을 찾는다. 이름으로 성적표를 찾거나 과목명으로 점수를 찾는 것처럼 식별 키와 값을 연결하는 자료에 적합하다.
Set은 원소 자체의 중복을 막고, Map은 키의 중복을 막는다. Map의 값은 서로 같을 수 있다.
11. 중첩 HashMap을 이용한 성적 관리
복합 자료의 구성
학생별 여러 과목 점수를 저장하려면 컬렉션의 값을 다시 컬렉션으로 구성할 수 있다. 강의록 예제는 학생 명단을 ArrayList<String>으로 두고, 한 학생의 개별 성적을 Map<String, Integer>에 저장한다. 전체 성적부는 학생 이름을 키로 하고 개별 성적 Map을 값으로 가지는 바깥쪽 HashMap이다.
Map<String, Integer> lectures;
Map<String, Map> scores = new HashMap<>();
List<String> names = new ArrayList<>();
names.add("김철수");
names.add("이영희");
각 학생마다 새 HashMap<String, Integer>를 만들어 국어·영어·수학 점수를 넣고, scores.put(name, lectures)로 이름과 개별 성적을 연결한다.
lectures = new HashMap<String, Integer>();
lectures.put("국어", 100);
lectures.put("영어", 95);
lectures.put("수학", 80);
scores.put("김철수", lectures);
중첩 조회
학생 이름으로 바깥쪽 Map에서 개별 성적 Map을 얻고, 다시 과목명을 키로 사용해 점수를 얻는다.
System.out.println(scores.get(name).get("국어"));
System.out.println(scores.get(name).get("영어"));
System.out.println(scores.get(name).get("수학"));
scores.get(name)은 한 학생의 과목별 성적 Map을 반환하고, 이어지는 get("국어")는 그 Map에서 국어 점수를 반환한다. 명단은 Iterator로 순회하여 학생별 성적을 차례로 출력할 수 있다.
복합 자료에서는 각 단계의 키와 값 자료형을 먼저 설계해야 한다. 바깥 Map의 이름 → 개별 성적, 안쪽 Map의 과목 → 점수 관계를 구분하면 중첩된 get() 호출을 이해하기 쉽다.
핵심 개념 정리
- 컬렉션은 여러 원소를 하나의 그룹으로 묶어 관리하는 자료구조이다.
- JCF는 컬렉션을 위한 표준 인터페이스와 구현 클래스를 통합하여 제공한다.
Set은 중복을 허용하지 않고,List는 순서와 중복을 허용한다.Queue는 FIFO 삽입·삭제를 지원하고,Map은 중복되지 않는 키와 값을 연결한다.Set,List,Queue는Collection계층이지만Map은 별도 계층이다.- JCF 타입은
java.util에 있으며 제네릭 타입 인자로 저장 자료형을 지정한다. Collection은add,remove,contains,size,clear,iterator등의 공통 기능을 정의한다.HashSet은 중복을 제거하며 입력 순서와 인덱스를 보장하지 않는다.ArrayList는 중복과 입력 순서를 유지하고 인덱스로 원소에 접근한다.set(index, value)는 기존 값을,remove(index)는 삭제된 값을 반환한다.Iterator는hasNext()와next()로 저장 방식과 무관하게 원소를 순회한다.forEach()에는 원소를 처리할Consumer또는 람다식을 전달할 수 있다.LinkedList는List와Queue기능을 제공하며 스택 메소드도 사용할 수 있다.HashMap은 해싱을 이용해 키와 값을 관리하고, 같은 키로put()하면 값이 변경된다.- Map의 값을 다시 Map으로 두면 학생별 과목 점수처럼 계층적인 자료를 표현할 수 있다.
컬렉션 선택의 핵심은 구현 클래스 이름을 외우는 것이 아니라 중복 허용 여부, 순서와 인덱스의 필요성, FIFO 처리, 키 기반 조회 중 어떤 성질이 필요한지를 먼저 판단하는 데 있다. 공통 인터페이스와 반복 방식을 이해하면 구현이 달라도 일관된 코드로 자료를 관리할 수 있다.
예상문제 20선
1. JCF에 대한 설명으로 가장 적절한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
JCF는 Java Collections Framework의 약자로 컬렉션의 공통 기능을 정의한 인터페이스와 실제 저장 방식을 구현한 클래스들을 제공한다.
2. JCF 인터페이스의 특징을 올바르게 설명한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④Map은 각 값을 고유한 키와 연결한다. 같은 키에 다시 값을 넣으면 기존 매핑의 값이 변경된다.
3. JCF 계층 관계에 대한 설명으로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
원소 자체를 모으는 Set, List, Queue는 Collection과 연결되지만 키-값 쌍을 다루는 Map은 별도 구조이다.
4. 중복을 허용하지 않고 원소의 순서가 중요하지 않은 컬렉션 선언은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①Set은 중복 없는 집합을 나타내며 HashSet은 대표적인 해시 기반 구현 클래스이다.
5. 현재 컬렉션과 주어진 컬렉션에 공통으로 있는 원소만 남기는 메소드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④retainAll()은 주어진 컬렉션에도 포함된 원소만 유지하므로 두 컬렉션의 교집합에 해당하는 원소가 남는다.
6. 컬렉션이 비어 있는지를 확인하는 메소드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②isEmpty()는 원소가 하나도 없으면 true를 반환한다. 원소 수 자체는 size()로 구한다.
7. HashSet에 이미 있는 문자열과 내용이 같은 새 String 객체를 add()하면?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①HashSet은 동등한 원소의 중복을 허용하지 않는다. 내용이 같은 문자열은 이미 존재하는 원소로 판단된다.
8. ArrayList의 특징으로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③ArrayList는 List 구현체로 순서와 중복을 지원하고 위치 기반 메소드를 제공한다.
9. E set(int index, E element)의 반환값은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③set()은 지정 위치의 값을 새 원소로 바꾸고 교체되기 전의 원소를 반환한다.
10. [one, one, two, three, five]에서 lastIndexOf("one")의 결과는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
인덱스는 0부터 시작하고 마지막 one은 두 번째 원소이므로 위치는 1이다.
11. List에서 remove(Object)와 remove(int)의 반환값을 올바르게 비교한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
객체를 지정한 삭제는 성공 여부인 boolean을, 인덱스를 지정한 삭제는 해당 위치에서 제거된 E 원소를 반환한다.
12. Iterator를 이용한 표준 순회 순서로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
컬렉션의 iterator()가 반복자를 반환하며, 반복문에서는 hasNext()가 참인 동안 next()로 다음 원소를 가져온다.
13. 컬렉션의 forEach()에 전달하는 함수형 인터페이스는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①Consumer<E>의 accept(E)가 각 원소에 적용되며, 람다식으로 간결하게 전달할 수 있다.
14. LinkedList를 큐로 사용할 때 FIFO 동작에 맞는 메소드 조합은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③offer()는 큐 뒤에 원소를 추가하고 poll()은 앞 원소를 삭제해 반환하므로 먼저 들어온 값이 먼저 나온다.
15. 큐의 앞 원소를 삭제하지 않고 읽는 메소드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②peek()은 큐의 앞 원소를 읽되 삭제하지 않는다. poll()은 앞 원소를 삭제하고 반환한다.
16. Map에서 이미 존재하는 키로 put(key, value)를 다시 호출하면?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
Map의 키는 고유하므로 같은 키로 다시 put()하면 새 매핑이 추가되지 않고 기존 값이 교체된다.
17. Map에 저장된 모든 키를 Set 형태로 얻는 메소드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③keySet()은 Map에 있는 키들로 구성된 Set<K>를 반환한다. 값의 모음은 values()로 얻는다.
18. HashMap에 대한 설명으로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④HashMap은 해시 기반의 Map 구현체이며 목록의 위치가 아니라 키를 이용해 대응 값을 조회한다.
19. scores.get(name).get("국어")의 처리 순서로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
바깥 Map의 값이 다시 과목-점수 Map이다. 첫 get(name)이 개별 성적을, 두 번째 get("국어")가 해당 점수를 반환한다.
20. 학생별 과목 성적을 표현하는 자료구조 설계로 가장 적절한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
바깥 Map은 이름과 개별 성적을 연결하고, 안쪽 Map은 과목과 점수를 연결하므로 자료의 두 단계 관계를 자연스럽게 표현한다.
댓글
댓글 쓰기