최근 C++과 Java 두 언어를 살펴보면서 자료구조 라이브러리(각각 STL과 컬렉션 프레임워크)에서 느껴지는 차이점이 흥미로웠습니다. 🤩 언어별로 용어가 다르거나 인터페이스가 달라 헷갈리는 경우도 있었고, 알게된 것들을 더 잘 기억하기 위해 Java의 컬렉션(Collection) 프레임워크에 대해 간단하게 정리해보겠습니다 :)

Collection이란?

Java에서 컬렉션(Collection) 은 데이터(객체)를 담고 관리하는 그릇, 즉 컨테이너(container) 역할을 하는 패키지입니다. 자바의 컨테이너는 크게 List 계열, Set 계열, Map 계열 3가지로 구분할 수 있어요.

주의해야할 점은, ListSet 은 Collection의 하위 인터페이스지만 Map 은 둘과 구분되는 인터페이스라는 것입니다. Map 은 Collection이 아니지만, Collection과 마찬가지로 데이터를 담는 역할을 하는 친구이기 때문에 함께 정리할게요.

Collection 프레임워크의 특징

다른 언어들과 비슷하게 자바의 컬렉션 프레임워크는 다음과 같은 특징을 가집니다

  • 가변적인 크기: 배열과 달리 크기가 동적으로 변합니다.
  • 표준화된 API: List, Set, Queue, Map 등 인터페이스가 표준화되어 있어 사용법이 일관적입니다.
  • 검증 및 최적화: 이미 구현된 컬렉션 클래스는 검증과 최적화가 되어 있어 직접 자료구조를 구현할 필요가 없습니다.
  • 멀티스레드 환경 지원: ConcurrentHashMap, CopyOnWriteArrayList 등 멀티스레드 환경에서 안전하게 사용할 수 있는 컬렉션 클래스도 제공됩니다.

Collection 계열

  • Collection은 객체 단위로 저장한다는 특징이 있습니다. (Map은 Key-Value 단위 저장)
  • java.util 패키지에서 제공합니다.
    • import java.util.Collection;을 통해 사용할 수 있습니다.
    • 보통은 import java.util.List;import java.util.ArrayList;처럼 하위 인터페이스나 구현 클래스를 더 자주 사용합니다.

List

  • List의 특징:

    • 순서 있음
      • 인덱스로 접근이 가능하고
      • 순차적 삽입/삭제에 유리
    • 중복 허용
    • Iterable
  • 대표 타입

    • ArrayList: 동적 배열 기반
    • LinkedList: 이중 연결 리스트 기반
    • Vector: 동기화 지원, 동적 배열 기반 (레거시)
    • Stack: LIFO 구조, Vector 상속 (레거시)
  • 주요 메서드

    • add(E e): 요소 추가
    • add(int index, E e): 지정 위치에 요소 추가
    • get(int index): 인덱스로 요소 조회
    • set(int index, E e): 지정 위치의 요소 교체
    • remove(int index): 인덱스로 요소 삭제
    • remove(Object o): 요소 삭제
    • contains(Object o): 요소 포함 여부 확인
    • size(): 요소 개수 반환
    • indexOf(Object o): 요소의 인덱스 반환
    • iterator(): 반복자 반환

Set

  • Set의 특징

    • 순서 보장X
      • 단, LinkedHashSet은 삽입 순서 보장
      • TreeSet은 자동 정렬
    • 중복 불가
      • Set의 특징을 이용하여 중복 데이터 제거할 때 유용
    • Iterable
  • 대표 타입

    • HashSet: 해시 테이블 기반, 순서 보장X
    • LinkedHashSet: 삽입 순서 보장
    • TreeSet: 이진 탐색 트리(레드-블랙 트리) 기반, 오름차순 정렬 보장
  • 주요 메서드

    • add(E e): 요소 추가 (중복 불가)
    • remove(Object o): 요소 삭제
    • contains(Object o): 요소 포함 여부 확인
    • size(): 요소 개수 반환
    • iterator(): 반복자 반환
    • isEmpty(): 비어있는지 확인

Queue

  • 특징

    • 일반적으로 FIFO(선입선출) 구조 (혹은 후입선출(LIFO))
    • 일부는 우선순위 기반(PriorityQueue)
  • 대표 타입

    • LinkedList: Queue 인터페이스 구현 시 FIFO로 동작
    • PriorityQueue: 우선순위 큐, 힙 기반
  • 주요 메서드

    • offer(E e): 요소 추가
    • poll(): 첫 번째 요소 반환 및 삭제
    • peek(): 첫 번째 요소 조회 (삭제X)
    • remove(): 첫 번째 요소 반환 및 삭제 (비어있으면 예외 발생)
    • element(): 첫 번째 요소 조회 (비어있으면 예외 발생)
    • size(): 요소 개수 반환
    • isEmpty(): 비어있는지 확인

Deque

  • 특징

    • 양쪽에서 삽입/삭제 가능한 큐
  • 대표 타입

    • ArrayDeque: 배열 기반, 양쪽 입출력
    • LinkedList: Deque 인터페이스 구현 시 양쪽 입출력 가능
  • 주요 메서드

    • offerFirst(E e): 앞쪽에 요소 추가
    • offerLast(E e): 뒤쪽에 요소 추가
    • pollFirst(): 앞쪽 요소 반환 및 삭제
    • pollLast(): 뒤쪽 요소 반환 및 삭제
    • peekFirst(): 앞쪽 요소 조회 (삭제X)
    • peekLast(): 뒤쪽 요소 조회 (삭제X)
    • removeFirst(): 앞쪽 요소 반환 및 삭제 (비어있으면 예외 발생)
    • removeLast(): 뒤쪽 요소 반환 및 삭제 (비어있으면 예외 발생)
    • size(): 요소 개수 반환
    • isEmpty(): 비어있는지 확인

Map 계열

  • Map의 특징

    • 키를 기준으로 값을 저장하여 Key-Value 쌍을 저장
      • Key는 중복 불가, Value는 중복 가능
    • 조회 속도 빠름
  • 대표 타입

    • HashMap: 해시 테이블 기반, 순서 보장X
    • LinkedHashMap: 삽입 순서 또는 접근 순서 보장
    • TreeMap: 이진 탐색 트리 기반, 정렬 보장
    • EnumMap: Enum 타입 키에 특화
    • WeakHashMap: 약한 참조 기반
    • IdentityHashMap: 객체 식별 기반
    • Hashtable: 동기화 지원, (레거시)
  • 주요 메서드:

    • put(K key, V value): 키-값 쌍 추가
    • get(Object key): 키로 값 조회
    • remove(Object key): 키로 값 삭제
    • containsKey(Object key): 키 포함 여부 확인
    • containsValue(Object value): 값 포함 여부 확인
    • keySet(): 키 집합 반환
    • values(): 값 집합 반환
    • entrySet(): 키-값 쌍 집합 반환
    • size(): 요소 개수 반환
    • isEmpty(): 비어있는지 확인

🤔 레거시(Legacy) 컬렉션은 무엇일까?

Java의 레거시 컬렉션이란, Java 2(JDK 1.2) 이후 등장한 **컬렉션 프레임워크(Collection Framework)**가 도입되기 전에 사용되던 구형 컬렉션 클래스를 의미합니다.

  • Vector
    • 동적 배열 기반의 자료구조로, 동기화(Synchronized) 지원
    • List 인터페이스를 구현한 클래스(요소에 순서가 있고, 중복 허용)
    • 내부적으로 모든 메서드가 동기화되어 있어, 멀티스레드 환경에서 안전하게 사용할 수 있음
    • 하지만 오늘날에는 ArrayListCollections.synchronizedList() 조합을 더 많이 사용

[예시] Vector

Vector<String> vector = new Vector<>();
vector.add("apple");
vector.add("banana");
System.out.println(vector); // [apple, banana]
  • Stack
    • LIFO(Last In First Out, 후입선출) 구조의 자료구조
    • Vector 클래스를 상속받아 구현됨
    • push(), pop(), peek() 등 스택 연산 지원
    • 현재는 Deque 인터페이스(예: ArrayDeque)를 사용하는 것을 권장

[예시] Stack

Stack<String> stack = new Stack<>();
stack.push("apple");
stack.push("banana");
System.out.println(stack.pop()); // banana
  • Hashtable
    • Key-Value 쌍을 저장하는 해시 기반의 자료구조
    • 동기화(Synchronized) 지원
    • Map 인터페이스를 구현한 클래스(키는 중복 불가, 값은 중복 가능)
    • 현재는 HashMapConcurrentHashMap이 더 많이 사용

[예시] Hashtable

Hashtable<String, Integer> table = new Hashtable<>();
table.put("apple", 1);
table.put("banana", 2);
System.out.println(table.get("apple")); // 1
  • 문제점
    • 성능 저하: 내부적으로 모든 메서드가 동기화되어 있어, 단일 스레드 환경에서는 불필요한 성능 저하가 발생할 수 있음
    • 기능 제한: 컬렉션 프레임워크의 다양한 인터페이스와 기능을 지원하지 않음
    • 유지보수 어려움: 레거시 코드는 최신 코드와 호환성이 떨어질 수 있음
  • 대안
    • Vector → ArrayList
      • 동기화가 필요하다면 Collections.synchronizedList(new ArrayList<>()) 사용
    • Stack → ArrayDeque
      • ArrayDeque는 스택과 큐 모두로 활용 가능
    • Hashtable → HashMap
      • 동기화가 필요하다면 ConcurrentHashMap 사용

✅ 결론: 컬렉션 프레임워크의 ArrayList, ArrayDeque, HashMap, ConcurrentHashMap 사용을 권장합니다.

🤔 Collection과 Collections의 차이는 무엇일까?

Collections는 데이터를 담는 컨테이너가 아니라 컨테이너(List, Set, Map 등)를 조작하거나 생성하는 유틸 메서드 묶음 (쉽게 표현하자면 도구 상자⚒️)입니다.
import java.util.Collection;을 통해 사용할 수 있습니다.

[예시] Collections를 import해서 sort 메서드 사용

import java.util.Collections;

List<String> list = Arrays.asList("C", "A", "B");
Collections.sort(list);  

✅ 즉, Collection은 컨테이너 모음, Collections는 컨테이너를 다루기 위한 도구 모음 패키지

🤔 Map은 Iterable할까?

자바에서 Map 인터페이스는 Iterable을 직접 구현하지 않습니다.
즉, Map 객체 자체로는 iterator() 메서드를 직접 호출할 수 없습니다.
Map은 Collection 계층이 아닌 별의 인터페이스이자 key-value 쌍을 저장하는 구조이기 때문입니다.

하지만 Map의 key, value, 또는 entry(키-값 쌍)를 Set이나 Collection 형태로 얻어온 후에는 Iterable을 사용할 수 있습니다.

예를 들어,

  • map.keySet(): 키들의 집합(Set) 반환 → Iterable
  • map.values(): 값들의 집합(Collection) 반환 → Iterable
  • map.entrySet(): 키-값 쌍의 집합(Set) 반환 → Iterable

이렇게 얻은 Set이나 Collection 객체는 Iterable을 구현하므로, for-each문이나 Iterator를 사용해 순회할 수 있습니다.

[예시] Map으로부터 Iterable한 entrySet을 만든 경우

import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Map.Entry;

public class MapIterationExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        map.put("orange", 3);

        // 1. for-each 루프로 entrySet 순회
        for (Entry<String, Integer> entry : map.entrySet()) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }

        // 2. Iterator로 entrySet 순회
        Set<Entry<String, Integer>> entrySet = map.entrySet();
        Iterator<Entry<String, Integer>> entryIterator = entrySet.iterator();

        while (entryIterator.hasNext()) {
            Entry<String, Integer> entry = entryIterator.next();
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }
    }
}

✅ 결론: Map 자체는 Iterable이 아니지만, entrySet() 등으로 얻은 Set 객체는 Iterable합니다.

cf) C++의 std::map은 직접적으로 “Iterable”이라는 인터페이스를 구현하는 것은 아니지만, STL 컨테이너들은 모두 반복자(iterator)를 제공하여 순회가 가능합니다.

#include <iostream>
#include <map>
#include <string>
int main() {
    std::map<std::string, int> myMap;
    myMap["apple"] = 1;
    myMap["banana"] = 2;
    myMap["orange"] = 3;

    // 범위 기반 for문 (C++11 이상)
    for (const auto& pair : myMap) {
        std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;
    }

    // 반복자를 이용한 순회
    for (auto iter = myMap.begin(); iter != myMap.end(); ++iter) {
        std::cout << "Key: " << iter->first << ", Value: " << iter->second << std::endl;
    }
}

즉, Java의 Map과 다르게 별도의 변환이 없어도 C++의 map은 iterator나 범위 기반 for문을 통해 직접 순회가 가능하기 때문에 실질적으로 iterable하다고 할 수 있습니다. 🤩

글을 마치며

각 컬렉션 및 타입들의 주요 특징을 이해하면, 상황에 맞는 효율적인 코드를 쉽고 빠르게 작성할 수 있습니다. 공부한 내용을 정리하다 보니 글에 부족한 부분이나 잘못된 내용이 있을 수 있습니다. 잘못된 점이나 보완하면 좋을 점, 또는 궁금한 점이 있다면 언제든 댓글로 남겨주세요!

👀 Reference

C++과 Java를 비교하면서 설명하는 좋은 블로그를 발견해서 링크를 함께 남깁니다 :)