1. Wrapper 클래스 (오토 박싱, 오토 언박싱)
자바에서 데이터 기본 단위는 객체이다. int, double과 같은 primitive type도 객체로 관리할 수 있도록 하는 것이 좋고, 이를 도와주는 클래스가 Wrapper 클래스이다.
- `int` → `Integer`, `long` → `Long`, `float` →`Float`, `double` → `Double`,
- `char` → `Character`, `boolean` → `Boolean`
* Collection 프레임워크에는 반드시 객체가 들어가야한다. 즉, primitive type을 쓸 수 없고 Wrapper 클래스로 감싸 들어가야한다.
Wrapper 클래스는 클래스이기 때문에 원래는 생성과 대입이 어렵지만, 자바에서는 오토 박싱, 오토 언박싱을 지원한다.
// 오토 박싱, 오토 언박싱이 없을 때
Integer intWrap = Integer.valueOf(10);
int number = intWrap.intValue();
// 오토 박싱, 오토 언박싱 사용
Integer intWrap = 10; // Auto Boxing
int number = intWrap; // Auto Unboxing
오토 박싱, 오토 언박싱이 없을 때는 위 방법처럼 `static` 메소드를 이용해 값을 대입해야하고, `intValue()`를 이용해 값을 가져올 수 있었다. 하지만 우리에겐 오토 박싱과 오토 언박싱이 있기 때문에 이를 신경쓰지 않고 그냥 primitive type처럼 간단하게 쓸 수 있다.
2. 제네릭 (Generic)
제네릭이 없을 때는 아래와 같이 Object 객체를 이용해 모든 객체를 담았다.
public class Box {
private Object item;
public Box(Object item) {
this.item = item;
}
public Object getItem() {
return item;
}
}
public class Main() {
public static void main(String[] args) {
Box box = new Box(new Apple(10));
Apple apple = (Apple)box.getItem(); // 다운캐스팅 반드시 필요
System.out.println(apple.getSugarContent()); // 다운캐스팅해야 메소드 호출 가능
}
}
Object 클래스를 사용할 경우 담은 객체의 메소드를 사용하기 위해서는 다운캐스팅이 항상 필요하다. 왜냐하면 Object 클래스의 scope에서 Apple 클래스의 메소드 `apple.getSugarContent()` 를 호출할 수 없기 때문이다. 그래서 매번 다운캐스팅을 해줘야하는데 이는 매우 번거로운 과정이다. 이러한 번거로움을 해결하기 위해 제네릭(Generic)이 도입되었다.
제네릭을 적용한 코드는 아래와 같다.
public class Box<T> {
private T item;
public Box(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
public class Main() {
public static void main(String[] args) {
Box<Apple> box = new Box(new Apple(10));
Apple apple = box.getItem(); // 다운캐스팅 불필요
System.out.println(apple.getSugarContent()); // Apple 타입으로 바로 메소드 호출 가능
}
}
제네릭을 사용하면 Box 클래스의 item을 담는 타입 자체가 Apple 타입이 된다. 따라서 Apple 타입으로 다시 다운캐스팅 할 필요 없이, 이미 타입이 Apple이므로 바로 `apple.getSugarContent()` 메소드 호출이 가능해진다.
이 제네릭 덕분에 Collection에서 컨테이너의 타입을 우리가 담을 객체의 타입으로 지정할 수 있는 것이고, 객체를 담았다가 뺄 때 별도의 다운캐스팅이 필요하지 않게 된다.
3. Java Collection Framework (JCF)

Collection 프레임워크는 자바에서 객체들을 저장하고 관리하기 위해 사용하는 일종의 컨테이너 클래스의 집합이다. 우리가 일반적으로 많이 사용하는 자료구조들이 이미 구현되어있고, 삽입, 삭제, 수정 등의 메소드도 이미 구현되어있어서 가져다 쓰면 된다. Collection 프레임워크는 크게 `Collection` 인터페이스 계열과 `Map` 인터페이스 계열로 나뉜다. `Collection` 계열은 단일 객체를 저장한다. `List`, `Set`, `Queue`가 존재하고, `Map` 계열은 Key-Value의 쌍으로 데이터를 저장하는 구조를 갖는다.
- List 계열: 데이터를 순차적으로 나열하며 중복 저장을 허용한다. 배열처럼 인덱스로 접근 가능하다. (e.g. `ArrayList`, `LinkedList`)
- Set 계열: 중복을 허용하지 않는 집합이다. 사용자 정의 객체를 담을 때는 반드시 `equals()`와 `hashCode()`를 재정의해야 중복을 정상적으로 차단할 수 있다. (IntelliJ에서 코드 자동 생성이 가능하다.) (e.g. `HashSet`, `LinkedHashSet`, `TreeSet`)
- Map 계열: 키(Key)와 값(Value)의 쌍으로 저장하고, Collection 인터페이스를 상속받지 않는 별도 계열이다. (e.g. `HashMap`, `LinkedHashMap`, `TreeMap`)
- Queue 계열: 선입선출(FIFO) 구조의 자료구조이다. (e.g. `Queue`, `Deque`)
각각의 자료구조들을 하나씩 자세히 살펴보자. 아래 자료구조들은 `import java.util.*;`을 통해 사용할 수 있다.
1. ArrayList
List<String> list = new ArrayList<>();
list.add("A"); // O(1) 삽입
list.get(0); // O(1) 조회
list.set(0, "B"); // O(1) 수정 (인덱스 0을 B로)
list.remove(0); // O(n) 삭제 (인덱스 0 삭제)
list.size(); // O(1) 크기 확인
list.contains("A"); // O(n) 특정 요소가 포함됐는지 확인
list.indexOf("A"); // O(n) 특정 요소의 인덱스 확인
list.clear(); // 전체 삭제
ArrayList는 우리가 일반적으로 가장 자주 사용하는 배열 형태이다. 데이터들을 메모리 상에서 연속으로 옆에 이어붙여서 저장한다는 것이 가장 큰 특징이다. 덕분에 인덱스 기반 조회시 속도가 빠르지만, 중간에 삽입, 삭제가 빈번하면 속도에 불리하다.
List로 타입을 선언하는 것은 유연성 때문이다. List 계열의 표준화된 메서드를 다운캐스팅 없이도 사용할 수 있다. 또한 SOLID 설계원칙 중 DIP (구현이 아닌 인터페이스에 맞춰 프로그래밍하기)에 맞는 사용이다. `new ArrayList<String>();` 이런식으로 사용하지 않고 타입을 비워두는데, 그 이유는 Java7 이후 왼쪽의 타입을 보고 추론 가능하기 때문이다. 그래서 빈 괄호로 두는 것이 표준이다. 뒤의 괄호`( )`는 생성자인데 초기 배열의 용량을 설정할 때 사용할 수 있으나 일반적으로 비워두면 자동으로 배열 용량이 맞춰줘 설정된다.
2. LinkedList
List<String> list = new LinkedList<>();
list.add("A"); // O(1) 삽입: 마지막에 추가
list.add(1, "Start"); // O(n) 삽입: 중간 삽입 (단, ArrayList와 달리 밀어내기 없음)
list.get(0); // O(n) 조회: 특정 인덱스까지 노드를 타고 순차적으로 이동해야 함
list.set(0, "New"); // O(n) 수정: 수정할 위치까지 탐색 시간이 소요됨
list.remove(2); // O(n) 삭제: 중간 요소 삭제는 탐색 시간 O(n)이 포함됨
list.size(); // O(1) 내부적으로 size 변수를 관리함
list.contains("A"); // O(n) 처음부터 끝까지 순회하며 찾아야 함
list.indexOf("A"); // O(n) 순회하며 일치하는 노드 인덱스 반환
list.clear(); // O(n) 모든 노드 간의 연결을 끊어야 하므로 전체 순회
LinkedList는 데이터들이 메모리에 흩어져서 존재한다. 각 노드는 내 데이터와 다음 노드의 주소를 함께 저장하여 체인처럼 이어진다. 인덱스 기반 조회가 느린데 메모리 상에 흩어져있어서 포인터를 따라가면서 일일이 찾아야하기 때문이다. 중간에 데이터를 삭제하거나 삽입할 때 삽입과 삭제 자체에는 O(1)이 소요된다. 그러나 해당 부분까지 찾아가는데 O(n)이 든다는 것을 주의해야한다. 이러한 특성 때문에 실전에서 잘 사용되지는 않고, 특화된 문제나 삽입, 삭제가 정말 빈번한 경우 사용된다.
3. HashSet
Set<String> set = new HashSet<>();
set.add("A"); // O(1) 삽입: 해시 함수를 통해 즉시 저장 위치 결정
set.contains("A"); // O(1) 조회: 해시 값을 계산해 즉시 확인
set.remove("B"); // O(1) 삭제: 해시 값을 찾아 즉시 제거
set.size(); // O(1) 크기 확인
set.isEmpty(); // O(1) 비어있는지 확인
set.clear(); // O(n) 모든 버킷을 비워야 하므로 요소 개수에 비례
HashSet은 데이터를 중복 없이 관리해야할 때 사용하는 자료구조이다. 순서가 중요하지 않고, 특정 데이터가 존재하는지 여부만 빠르게 판단해야할 때 사용한다. 내부적으로 해시 테이블을 사용한다. 데이터를 저장하기 전 객체의 `hashCode()`를 호출해 해시값을 만들고 인덱스로 변환한다. 이후 `equals()` 메소드를 이용해 비교해서 동일한 객체라면 저장하지 않는다. 이 때문에 사용자 정의 객체를 담을 때는 반드시 `equals()`와 `hashCode()`를 재정의해야 중복을 정상적으로 차단할 수 있다. (두 메소드는 IntelliJ에서 코드 자동 생성이 가능하다.) 해시 테이블 구조이기 때문에 조회가 이론적으로 O(1)에 동작한다.
4. TreeSet
TreeSet<Integer> set = new TreeSet<>();
set.add(50); // O(log n) 삽입: 위치를 찾으며 트리를 탐색
set.contains(30); // O(log n) 조회: 루트 노드부터 대소 관계를 비교하며 탐색
set.remove(10); // O(log n) 삭제: 삭제 후 트리의 균형을 맞추는 작업 포함
set.first(); // O(1) 최솟값 반환 (트리의 가장 왼쪽 끝)
set.last(); // O(1) 최댓값 반환 (트리의 가장 오른쪽 끝)
set.higher(30); // O(log n) 30보다 큰 값 중 가장 가까운 값 반환
set.lower(30); // O(log n) 30보다 작은 값 중 가장 가까운 값 반환
set.size(); // O(1) 크기 확인
set.clear(); // O(n) 모든 노드 제거
TreeSet은 레드-블랙 트리라는 자가균형 이진탐색트리로 구현되어있다. 자가균형 트리이므로 데이터가 치우쳐서 저장되는 것을 걱정할 필요없다. 데이터를 삽입할 때 자동으로 오름차순 정렬되어 저장되고, Set이기 때문에 중복 저장은 안된다. 또한, 트리 구조이기 때문에 삽입, 조회, 삭제에 log(n)이 걸리는 안정적인 성능을 보장한다. 정렬이 된다는 특징과 HashSet에 비해 안정적인 성능을 보장하기 때문에 더 자주 사용된다.
5. HashMap
Map<String, Integer> map = new HashMap<>();
map.put("Apple", 1000); // O(1) 삽입: 해시 함수로 위치 결정
map.put("Apple", 1500); // O(1) 수정: 동일한 Key가 들어오면 새로운 Value로 덮어씀
map.get("Apple"); // O(1) 조회: Key를 통해 Value를 즉시 찾음
map.remove("Banana"); // O(1) 삭제: 해당 Key와 쌍을 이루는 데이터 삭제
map.containsKey("Apple"); // O(1) 키 존재 여부 확인
map.containsValue(1500); // O(n) 값 존재 여부 확인: 모든 Value를 다 뒤져야 함
map.size(); // O(1) 크기 확인
map.getOrDefault("Pear", 0); // O(1) 키가 없으면 기본값 0 반환
map.keySet(); // 모든 Key를 Set 객체로 반환
map.values(); // 모든 Value를 Collection 객체로 반환
HashMap은 Key-Value의 딕셔너리 구조이다. 해시 테이블을 사용하기 때문에 대부분 작업이 O(1)이 소요된다. HashSet과 마찬가지로 사용자 정의 객체를 담을 때는 반드시 `equals()`와 `hashCode()`를 재정의해야 중복을 정상적으로 차단할 수 있다. (두 메소드는 IntelliJ에서 코드 자동 생성이 가능하다.)
6. TreeMap
TreeMap<Integer, String> map = new TreeMap<>();
map.put(3, "Three"); // O(log n) 삽입: 키를 비교하며 트리 내 위치 결정
map.get(2); // O(log n) 조회: 키의 대소 관계를 비교하며 탐색
map.remove(1); // O(log n) 삭제: 노드 제거 후 트리 균형 재조정
map.firstKey(); // O(1) 가장 낮은 키 반환 (가장 왼쪽 노드)
map.lastKey(); // O(1) 가장 높은 키 반환 (가장 오른쪽 노드)
map.higherEntry(2); // O(log n) 키 2보다 큰 데이터 중 가장 가까운 Key-Value 쌍
map.lowerKey(2); // O(log n) 키 2보다 작은 데이터 중 가장 가까운 키
map.size(); // O(1) 크기 확인
map.containsKey(3); // O(log n) 키 존재 여부 확인
TreeMap은 각 노드가 Key-Value 쌍으로 이루어진 레드-블랙 트리 기반의 자가균형 이진 탐색 트리이다. 마찬가지로 트리이기 때문에 삽입, 조회, 삭제에 log(n)이 소요된다.
7. Stack
Stack<Integer> stack = new Stack<>();
stack.push(10); // O(1) 삽입: 맨 위에 쌓기
stack.peek(); // O(1) 조회: 맨 위의 데이터를 확인만 함
stack.pop(); // O(1) 삭제: 맨 위의 데이터를 꺼내서 반환
stack.search(10); // O(n) 검색: 맨 위에서부터 몇 번째에 있는지 확인 (1부터 시작)
stack.isEmpty(); // O(1) 스택이 비어있는지 확인
stack.size(); // O(1) 요소 개수 확인
stack.clear(); // O(n) 모든 요소 삭제
Stack은 스택 자료구조를 구현한 것이다. Last In First Out (LIFO)의 특성을 갖는다. Stack 클래스는 Vector 클래스를 상속받아 구현되어있는데 Vector 클래스를 상속받았기 때문에 스택의 의도치 않은 메소드 (인덱스 접근 `get`, `insert`)를 사용할 수 있는 문제가 있다. 따라서 스택을 구현할 때 `ArrayDeque`를 사용하는 것이 실제에선 더 권장된다.
8. Queue (LinkedList)
Queue<String> queue = new LinkedList<>();
queue.add("First"); // O(1) 삽입: 큐의 맨 뒤에 추가 (공간 없으면 예외 발생)
queue.offer("Second"); // O(1) 삽입: 큐의 맨 뒤에 추가 (공간 없으면 false 반환, 권장)
queue.peek(); // O(1) 조회: 맨 앞(출구)에 있는 데이터를 확인만 함
queue.poll(); // O(1) 삭제: 맨 앞의 데이터를 꺼내서 반환 (비어있으면 null)
queue.size(); // O(1) 크기 확인
queue.isEmpty(); // O(1) 비어있는지 확인
queue.contains("First"); // O(n) 특정 요소 포함 여부 확인 (전체 순회)
Queue는 자료구조 큐를 구현한 것이다. 삽입은 뒤에서 삭제는 앞에서만 일어난다. 가장 일반적으로는 `LinkedList`를 이용해서 구현한다. `LinkedList`에서 삽입, 삭제가 맨 앞과, 맨 뒤에서만 일어나므로 O(1)이 보장되기 때문이다. `ArrayList`는 이동시켜야하기 때문에 더 느리다. 따라서 `LinkedList`로 구현하는 것이 일반적이다. 삽입과 삭제에는 일반적으로 `offer`와 `poll`을 사용하는 것이 일반적이다.
9. Deque (ArrayDeque)
Deque<String> deque = new ArrayDeque<>();
deque.addFirst("Front"); // O(1) 맨 앞에 추가
deque.addLast("Back"); // O(1) 맨 뒤에 추가
deque.offerFirst("New"); // O(1) 맨 앞에 추가 (성공 시 true 반환)
deque.peekFirst(); // O(1) 맨 앞 요소 확인
deque.peekLast(); // O(1) 맨 뒤 요소 확인
deque.pollFirst(); // O(1) 맨 앞 요소 꺼내기 (비어있으면 null)
deque.pollLast(); // O(1) 맨 뒤 요소 꺼내기 (비어있으면 null)
// 스택(Stack)으로 사용 시
deque.push("Data"); // O(1) addFirst()와 동일
deque.pop(); // O(1) removeFirst()와 동일
// 큐(Queue)로 사용 시
deque.offer("Data"); // O(1) addLast()와 동일
deque.poll(); // O(1) pollFirst()와 동일
deque.size(); // O(1) 크기 확인
deque.isEmpty(); // O(1) 비어있는지 확인
Deque는 덱을 구현한 자료구조이다. 내부적으로 순환배열 구조를 사용한다. 배열의 끝에 도달하면 다시 처음으로 돌아가 데이터를 저장하므로, 양방향 삽입, 삭제시 데이터를 밀어낼 필요가 없어 효율적으로 덱을 구현할 수 있다. 스택과, 큐를 `ArrayDeque`로 구현하는 것을 일반적으로 가장 권장한다. 모든 양 끝단 연산이 O(1)에 동작한다. `LinkedList`를 이용한 구현보다 메모리 사용량이 적고, 연속 메모리이기 때문에 캐시 지역성이 좋아서 더 높은 성능을 보여준다.
10 PriorityQueue
PriorityQueue<Integer> pq = new PriorityQueue<>(); // 최소 힙
PriorityQueue<Integer> reversePq = new PriorityQueue<>(Collections.reverseOrder()); // 최대 힙
pq.add(30); // O(log n) 삽입: 트리 구조를 타고 올라가며 위치 결정
pq.offer(10); // O(log n) 삽입
pq.peek(); // O(1) 조회: 루트 노드(최상단)의 값만 확인
pq.poll(); // O(log n) 삭제: 최상단 노드 추출 후 트리 재정렬
pq.size(); // O(1) 크기 확인
pq.clear(); // O(n) 모든 요소 삭제
pq.contains(20); // O(n) 전체 노드를 탐색해야 함 (힙은 전체 정렬이 아니므로)
PriorityQueue는 우선순위 큐를 구현한 것이다. 내부적으로는 완전 이진 트리 기반의 힙 구조이다. 배열을 이용해 트리 구조를 구현한다. 그냥 선언하면 최소 힙(Min Heap)으로 동작하여 작은 값이 먼저 나온다. 사용자 정의 객체를 넣으면 `Comparable` 인터페이스를 구현하거나, 생성시 `Comparator`를 전달해서 우선순위 기준을 직접 정해줘야한다. 최대 힙으로 구현하고 싶다면 위 코드처럼 `Collections.reverseOrder()`를 생성자로 전달해줘야한다.
'프로젝트 > Java' 카테고리의 다른 글
| [Java] 8. 람다함수, 함수형 프로그래밍, Stream API (0) | 2026.02.15 |
|---|---|
| [Java] 6. 예외처리 (0) | 2026.02.06 |
| [Java] 5. 문자열 (String, StringBuilder, StringBuffer), 자바 표준 입출력 (0) | 2026.01.29 |
| [Java] 4. 추상 클래스, 인터페이스 (0) | 2026.01.28 |
| [Java] 3. Object 클래스 (toString, equals, hashCode, clone) (0) | 2026.01.28 |