[2026] Java 컬렉션 | ArrayList, HashMap, Set
이 글의 핵심
Java 컬렉션: ArrayList, HashMap, Set. List 인터페이스·Set 인터페이스.
들어가며
컬렉션 프레임워크는 List·Set·Map 등으로 데이터를 담는 공통 설계도를 제공합니다. 같은 인터페이스 아래에 구현체만 바꿔 성능·순서 요구에 맞출 수 있습니다.
실무에서 마주한 현실
개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.
1. List 인터페이스
ArrayList
Java에서 가장 많이 사용하는 동적 배열 구조입니다: 다음은 java를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
다음은 java를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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) 자료구조입니다: 다음은 java를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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)
아래 코드는 java를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
다음은 java를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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)
아래 코드는 java를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.
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 클래스
다음은 java를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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. 실전 예제
예제: 학생 성적 관리
다음은 java를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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: 유틸리티 메서드