Java 컬렉션 | ArrayList, HashMap, Set
이 글의 핵심
Java 컬렉션에 대한 실전 가이드입니다. ArrayList, HashMap, Set 등을 예제와 함께 상세히 설명합니다.
들어가며
컬렉션 프레임워크는 List·Set·Map 등으로 데이터를 담는 공통 설계도를 제공합니다. 같은 인터페이스 아래에 구현체만 바꿔 성능·순서 요구에 맞출 수 있습니다.
1. List 인터페이스
ArrayList
Java에서 가장 많이 사용하는 동적 배열 구조입니다:
import java.util.ArrayList;
import java.util.List;
public class ArrayListExample {
public static void main(String[] args) {
// ArrayList<String>: String 타입만 저장하는 리스트
// List<String>: 인터페이스 타입으로 선언 (다형성)
// new ArrayList<>(): 다이아몬드 연산자 (타입 추론)
List<String> fruits = new ArrayList<>();
// add: 리스트 끝에 요소 추가 - O(1) 분할상환
fruits.add("사과");
fruits.add("바나나");
fruits.add("오렌지");
// add(index, element): 특정 위치에 삽입 - O(n)
// 인덱스 1 이후의 모든 요소를 오른쪽으로 이동
fruits.add(1, "딸기"); // [사과, 딸기, 바나나, 오렌지]
// get: 인덱스로 요소 접근 - O(1)
// 내부적으로 배열이므로 즉시 접근 가능
System.out.println(fruits.get(0)); // 사과
System.out.println("크기: " + fruits.size()); // 4
// set: 특정 인덱스의 값 변경 - O(1)
fruits.set(0, "청사과"); // [청사과, 딸기, 바나나, 오렌지]
// remove(index): 인덱스로 삭제 - O(n)
// 삭제 후 뒤의 요소들을 왼쪽으로 이동
fruits.remove(0); // [딸기, 바나나, 오렌지]
// remove(Object): 값으로 삭제 - O(n)
// 순회하며 equals()로 비교하여 찾음
fruits.remove("바나나"); // [딸기, 오렌지]
// 순회: enhanced for loop (for-each)
for (String fruit : fruits) {
System.out.println(fruit);
}
// contains: 요소 존재 여부 확인 - O(n)
// 순회하며 equals()로 비교
if (fruits.contains("오렌지")) {
System.out.println("오렌지 있음");
}
// clear: 모든 요소 제거 - O(n)
fruits.clear();
// isEmpty: 비어있는지 확인 - O(1)
System.out.println("비어있음: " + fruits.isEmpty()); // true
}
}
ArrayList의 내부 구조:
- 내부적으로 배열(
Object[])을 사용 - 초기 용량: 10 (기본값)
- 용량 부족 시: 1.5배로 확장 (재할당 + 복사)
- 인덱스 접근: O(1), 삽입/삭제: O(n)
시간 복잡도 정리:
| 연산 | 시간 복잡도 | 설명 |
|---|---|---|
get(index) | O(1) | 배열 인덱스 접근 |
add(element) | O(1) 분할상환 | 끝에 추가 |
add(index, element) | O(n) | 중간 삽입 (요소 이동) |
remove(index) | O(n) | 삭제 후 요소 이동 |
contains(element) | O(n) | 순회하며 검색 |
LinkedList
import java.util.LinkedList;
LinkedList<Integer> numbers = new LinkedList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
// 앞/뒤 추가
numbers.addFirst(0);
numbers.addLast(4);
// 앞/뒤 제거
numbers.removeFirst();
numbers.removeLast();
System.out.println(numbers); // [1, 2, 3]
2. Set 인터페이스
HashSet
중복을 허용하지 않는 집합(Set) 자료구조입니다:
import java.util.HashSet;
import java.util.Set;
public class HashSetExample {
public static void main(String[] args) {
// HashSet: 해시 테이블 기반 집합
// 중복 불가, 순서 보장 안 됨
Set<Integer> numbers = new HashSet<>();
// add: 요소 추가 - O(1) 평균
// 내부적으로 hashCode()를 계산하여 저장 위치 결정
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(1); // 중복 무시 (add()가 false 반환)
// HashSet은 순서를 보장하지 않음
// 출력 순서는 실행마다 다를 수 있음
System.out.println(numbers); // [1, 2, 3] (순서 무작위)
// contains: 요소 존재 여부 확인 - O(1) 평균
// hashCode()로 위치를 찾아서 equals()로 비교
if (numbers.contains(2)) {
System.out.println("2 있음");
}
// remove: 요소 삭제 - O(1) 평균
numbers.remove(2);
// 집합 연산 (수학의 집합 개념)
Set<Integer> set1 = new HashSet<>();
set1.add(1);
set1.add(2);
set1.add(3); // set1 = {1, 2, 3}
Set<Integer> set2 = new HashSet<>();
set2.add(2);
set2.add(3);
set2.add(4); // set2 = {2, 3, 4}
// 합집합 (Union): set1 ∪ set2
// 두 집합의 모든 요소 (중복 제거)
Set<Integer> union = new HashSet<>(set1); // set1 복사
union.addAll(set2); // set2의 모든 요소 추가
System.out.println("합집합: " + union); // [1, 2, 3, 4]
// 교집합 (Intersection): set1 ∩ set2
// 두 집합에 모두 있는 요소만
Set<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2); // set2에 있는 것만 유지
System.out.println("교집합: " + intersection); // [2, 3]
// 차집합 (Difference): set1 - set2
// set1에는 있지만 set2에는 없는 요소
Set<Integer> difference = new HashSet<>(set1);
difference.removeAll(set2); // set2에 있는 것 제거
System.out.println("차집합: " + difference); // [1]
}
}
HashSet의 중복 제거 원리:
add()호출 시 요소의hashCode()계산- 해시 코드로 저장 위치(버킷) 결정
- 같은 버킷에 이미 요소가 있으면
equals()로 비교 - 같은 요소면 추가하지 않음 (중복 제거)
시간 복잡도:
add(),remove(),contains(): O(1) 평균, O(n) 최악- 순서가 필요하면
LinkedHashSet(삽입 순서 유지) - 정렬이 필요하면
TreeSet(자동 정렬, O(log n))
TreeSet (정렬된 Set)
import java.util.TreeSet;
TreeSet<Integer> numbers = new TreeSet<>();
numbers.add(5);
numbers.add(2);
numbers.add(8);
numbers.add(1);
System.out.println(numbers); // [1, 2, 5, 8] (자동 정렬)
System.out.println("최소: " + numbers.first());
System.out.println("최대: " + numbers.last());
3. Map 인터페이스
HashMap
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
Map<String, Integer> ages = new HashMap<>();
// 추가
ages.put("홍길동", 25);
ages.put("김철수", 30);
ages.put("이영희", 28);
// 조회
System.out.println(ages.get("홍길동")); // 25
// 기본값
System.out.println(ages.getOrDefault("박민수", 0)); // 0
// 존재 여부
if (ages.containsKey("홍길동")) {
System.out.println("홍길동 있음");
}
// 수정
ages.put("홍길동", 26); // 덮어쓰기
// 없을 때만 추가
ages.putIfAbsent("홍길동", 27); // 무시됨
ages.putIfAbsent("박민수", 35); // 추가됨
// 삭제
ages.remove("김철수");
// 순회
for (String key : ages.keySet()) {
System.out.println(key + ": " + ages.get(key));
}
// 키-값 쌍 순회
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
TreeMap (정렬된 Map)
import java.util.TreeMap;
TreeMap<String, Integer> map = new TreeMap<>();
map.put("Charlie", 30);
map.put("Alice", 25);
map.put("Bob", 28);
System.out.println(map); // {Alice=25, Bob=28, Charlie=30} (키로 정렬)
4. 컬렉션 유틸리티
Collections 클래스
import java.util.*;
public class CollectionsExample {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5, 9));
// 정렬
Collections.sort(numbers);
System.out.println(numbers); // [1, 1, 3, 4, 5, 9]
// 역순 정렬
Collections.sort(numbers, Collections.reverseOrder());
System.out.println(numbers); // [9, 5, 4, 3, 1, 1]
// 섞기
Collections.shuffle(numbers);
// 최대/최소
System.out.println("최대: " + Collections.max(numbers));
System.out.println("최소: " + Collections.min(numbers));
// 빈도
System.out.println("1의 개수: " + Collections.frequency(numbers, 1));
}
}
5. 실전 예제
예제: 학생 성적 관리
import java.util.*;
class Student {
String name;
int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
}
public class GradeManager {
private Map<String, Student> students;
public GradeManager() {
students = new HashMap<>();
}
public void addStudent(String id, String name, int score) {
students.put(id, new Student(name, score));
}
public Student getStudent(String id) {
return students.get(id);
}
public double getAverage() {
if (students.isEmpty()) return 0;
int sum = 0;
for (Student student : students.values()) {
sum += student.score;
}
return (double) sum / students.size();
}
public List<Student> getTopStudents(int n) {
List<Student> list = new ArrayList<>(students.values());
list.sort((a, b) -> b.score - a.score);
return list.subList(0, Math.min(n, list.size()));
}
public static void main(String[] args) {
GradeManager manager = new GradeManager();
manager.addStudent("001", "홍길동", 95);
manager.addStudent("002", "김철수", 88);
manager.addStudent("003", "이영희", 92);
System.out.println("평균: " + manager.getAverage());
System.out.println("\n상위 2명:");
for (Student s : manager.getTopStudents(2)) {
System.out.println(s.name + ": " + s.score);
}
}
}
정리
핵심 요약
- List: ArrayList(빠른 조회), LinkedList(빠른 삽입/삭제)
- Set: HashSet(순서 없음), TreeSet(정렬됨)
- Map: HashMap(순서 없음), TreeMap(정렬됨)
- 제네릭:
<T>로 타입 안전성 - Collections: 유틸리티 메서드
다음 단계
- Java Stream API
- Java 람다
- Java 예외 처리
관련 글
- Rust 컬렉션 | Vec, HashMap, HashSet
- Java 시작하기 | JDK 설치부터 Hello World까지
- Java 변수와 타입 | 기본 타입, 참조 타입, 형변환
- Java 클래스와 객체 | OOP, 상속, 인터페이스
- Java Stream API | filter, map, reduce 완벽 정리