Day 16: 컬렉션 Set, Map
Set은 중복을 허용하지 않는 컬렉션이고, Map은 키-값 쌍으로 데이터를 저장하는 컬렉션입니다. Set은 주머니에 구슬을 넣되 같은 색 구슬은 하나만 보관하는 것, Map은 사전처럼 단어(키)로 뜻(값)을 찾는 것과 같습니다.
HashSet과 TreeSet
중복 없는 집합을 다루는 두 가지 구현체입니다.
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
public class SetExample {
public static void main(String[] args) {
// HashSet: 순서 보장 안 됨, O(1) 접근
Set<String> hashSet = new HashSet<>();
hashSet.add("Java");
hashSet.add("Python");
hashSet.add("Go");
hashSet.add("Java"); // 중복! 추가되지 않음
hashSet.add("Rust");
System.out.println("HashSet: " + hashSet); // 순서 불규칙
System.out.println("크기: " + hashSet.size()); // 4 (중복 제외)
System.out.println("Java 포함? " + hashSet.contains("Java")); // true
// LinkedHashSet: 삽입 순서 유지
Set<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("첫 번째");
linkedSet.add("두 번째");
linkedSet.add("세 번째");
System.out.println("LinkedHashSet: " + linkedSet); // 삽입 순서 유지
// TreeSet: 자동 정렬 (오름차순)
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(42);
treeSet.add(15);
treeSet.add(8);
treeSet.add(99);
treeSet.add(23);
System.out.println("TreeSet: " + treeSet); // [8, 15, 23, 42, 99]
// 집합 연산
Set<String> setA = new HashSet<>(Set.of("Java", "Python", "Go"));
Set<String> setB = new HashSet<>(Set.of("Python", "Rust", "Go"));
// 합집합
Set<String> union = new HashSet<>(setA);
union.addAll(setB);
System.out.println("합집합: " + union);
// 교집합
Set<String> intersection = new HashSet<>(setA);
intersection.retainAll(setB);
System.out.println("교집합: " + intersection);
// 차집합
Set<String> difference = new HashSet<>(setA);
difference.removeAll(setB);
System.out.println("차집합: " + difference);
}
}
HashMap 기본 사용법
키-값 쌍을 저장하는 가장 많이 사용되는 Map 구현체입니다.
import java.util.HashMap;
import java.util.Map;
public class HashMapBasic {
public static void main(String[] args) {
Map<String, Integer> scores = new HashMap<>();
// 추가
scores.put("홍길동", 85);
scores.put("김영희", 92);
scores.put("이철수", 78);
scores.put("박지민", 95);
// 조회
System.out.println("홍길동 점수: " + scores.get("홍길동")); // 85
System.out.println("없는 키: " + scores.get("최수진")); // null
System.out.println("기본값: " + scores.getOrDefault("최수진", 0)); // 0
// 수정 (같은 키로 put하면 값 덮어쓰기)
scores.put("홍길동", 90);
System.out.println("수정 후: " + scores.get("홍길동")); // 90
// 포함 여부
System.out.println("키 포함? " + scores.containsKey("김영희")); // true
System.out.println("값 포함? " + scores.containsValue(78)); // true
// 삭제
scores.remove("이철수");
System.out.println("크기: " + scores.size()); // 3
// 순회 방법들
// 1. entrySet (키-값 모두 필요할 때 가장 효율적)
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 2. keySet
for (String name : scores.keySet()) {
System.out.println(name + " -> " + scores.get(name));
}
// 3. forEach + 람다
scores.forEach((name, score) ->
System.out.println(name + " = " + score));
}
}
Map 고급 활용
Java 8 이후 추가된 편리한 메서드들을 알아봅니다.
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class MapAdvanced {
public static void main(String[] args) {
// 단어 빈도수 세기
String text = "Java는 객체지향 언어이다 Java는 플랫폼 독립적이다 Java는 안전하다";
String[] words = text.split(" ");
Map<String, Integer> wordCount = new HashMap<>();
for (String word : words) {
// merge: 키가 있으면 병합, 없으면 추가
wordCount.merge(word, 1, Integer::sum);
}
System.out.println("단어 빈도: " + wordCount);
// putIfAbsent: 키가 없을 때만 추가
Map<String, String> config = new HashMap<>();
config.put("host", "localhost");
config.putIfAbsent("host", "remote-server"); // 이미 있으므로 무시
config.putIfAbsent("port", "8080"); // 없으므로 추가
System.out.println("설정: " + config);
// computeIfAbsent: 캐시 패턴
Map<Integer, String> cache = new HashMap<>();
String result = cache.computeIfAbsent(42, key -> {
System.out.println("비용이 큰 계산 수행 중...");
return "결과-" + key;
});
System.out.println("첫 호출: " + result);
// 두 번째 호출: 이미 캐시에 있으므로 계산하지 않음
String cached = cache.computeIfAbsent(42, key -> "새 계산");
System.out.println("캐시 히트: " + cached);
// TreeMap: 키 기준 자동 정렬
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("banana", 3);
treeMap.put("apple", 5);
treeMap.put("cherry", 1);
System.out.println("TreeMap: " + treeMap); // 알파벳순
// 불변 Map 생성
Map<String, Integer> immutable = Map.of(
"one", 1,
"two", 2,
"three", 3
);
System.out.println("불변 Map: " + immutable);
}
}
실전 예제: 전화번호부
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class PhoneBook {
private final Map<String, List<String>> contacts = new HashMap<>();
void addContact(String name, String phone) {
contacts.computeIfAbsent(name, k -> new ArrayList<>()).add(phone);
System.out.println(name + "의 번호 " + phone + " 추가됨");
}
List<String> findByName(String name) {
return contacts.getOrDefault(name, List.of());
}
void removeContact(String name) {
if (contacts.remove(name) != null) {
System.out.println(name + " 삭제됨");
} else {
System.out.println(name + "을(를) 찾을 수 없음");
}
}
void printAll() {
System.out.println("=== 전화번호부 ===");
contacts.forEach((name, phones) -> {
System.out.println(name + ": " + String.join(", ", phones));
});
System.out.println("총 " + contacts.size() + "명의 연락처");
}
Map<String, Integer> getStatistics() {
Map<String, Integer> stats = new HashMap<>();
stats.put("총 연락처 수", contacts.size());
stats.put("총 전화번호 수", contacts.values().stream()
.mapToInt(List::size).sum());
return stats;
}
public static void main(String[] args) {
PhoneBook phoneBook = new PhoneBook();
phoneBook.addContact("홍길동", "010-1234-5678");
phoneBook.addContact("홍길동", "02-999-0000"); // 같은 이름, 다른 번호
phoneBook.addContact("김영희", "010-9876-5432");
phoneBook.addContact("이철수", "010-5555-3333");
phoneBook.printAll();
System.out.println("\n홍길동 번호: " + phoneBook.findByName("홍길동"));
System.out.println("통계: " + phoneBook.getStatistics());
}
}
오늘의 연습문제
-
중복 문자 찾기: 문자열에서 중복되는 문자와 중복되지 않는 문자를 각각 Set으로 분류하여 출력하세요. 입력:
"programming", 출력: 중복={g, r, m}, 유일={p, o, a, i, n}. -
학생 그룹핑:
Map<String, List<Student>>를 사용하여 학생들을 학점별로 그룹핑하세요. A학점 학생 목록, B학점 학생 목록 등으로 출력하세요. -
영한 사전:
TreeMap을 사용한 영한 사전을 구현하세요. 단어 추가, 검색, 특정 알파벳으로 시작하는 단어 목록 조회(subMap활용), 전체 단어 알파벳순 출력 기능을 만드세요.