본문 바로가기
Daily Dev Q&A 정리 템플릿

25.12.08 자바의 Collections Framework에 대해 설명해보기

by teg0 2025. 12. 8.

자바에서 "데이터의 집합(그룹)을 저장하고 효율적으로 처리하기 위한 표준화된 설계도(인터페이스)와 구현체(클래스)"들의 모음이다.
쉽게 말해, 데이터를 담는 "다양한 종류의 컨테이너(그릇) 세트"라고 보시면 됩니다. 상황에 따라 어떤 그릇(List, Set, Map)을 쓸지 골라서 사용해야 한다.

 

자료 구조와 연관이 되어 있기 때문에, 먼저 자료 구조를 알아보고 Collections Framework로 넘어가 보려고 한다.

 

자료구조

자료구조는 크게 단순 자료구조와 복합 자료구조로 나뉜다.

  • 단순 자료구조 원시타입(int, double, long 등)와 같이 컴퓨터가 기본적으로 제공하는 자료형을 의미
  • 복합 자료구조는 데이터가 순차적으로 연결되어 있는지에 따라 다시 선형 자료구조와 비선형 자료구조로 나뉜다.
    • 선형 자료구조는 데이터가 순차적으로 연결된 자료구조를 의미하며 리스트, 연결 리스트, 스택, 큐, 힙이 있다.
    • 비선형 자료구조는 하나의 데이터에 여러 데이터가 연결될 수 있고 여러 데이터가 하나의 데이터로 연결될 수 있는 비순차적인 자료구조를 의미하며 트리와 그래프가 있다.

 

여기서 Collection 인터페이스에 해당하는 List, Set, Queue는 선형 자료구조에 해당한다.

 

알고리즘

알고리즘은 어떤 문제를 해결하기 위한 절차를 의미한다.

컴퓨터 프로그램은 데이터를 처리해서 결과를 나타내는데 이때 데이터는 자료구조로 표현되고 데이터에 대한 처리 방법은 알고리즘으로 구현된다.

 

알고리즘의 조건

문제를 해결하는 절차가 기술되었다고 모두 알고리즘이라고 할 수 없다.

알고리즘이라고 불리는 기준은 5가지 충족되어야 한다.

  • 입력: 0개 이상의 입력이 존재해야 한다.
  • 출력: 1개 이상의 출력이 존재해야 한다.
  • 명백성: 각 명령어의 의미는 모호하지 않고 명확해야 한다.
  • 유한성: 한정된 수의 단계 후 반드시 종료가 되어야 한다.
  • 유효성: 각 명령어들은 실행 가능한 연산이어야 한다.

 

알고리즘 선택

주어진 문제를 해결하기 위해 작업을 단계별로 명확하게 정의한 것으로 같은 문제라도 푸는 방식은 다를 수 있으므로 주어진 문제에 적용 가능한 알고리즘도 다양한다. 하지만, 자원은 한정되어 있기 때문에 가능한 적은 자원으로 문제를 해결해야 한다.

그렇기에 상황에 따라 어떤 알고리즘이 최적인지를 먼저 확인하고 알고리즘을 사용해야 한다.

 

알고리즘의 최악 성능을 평가하는 빅오 표기법

자료구조를 사용했을 때 연산 속도가 얼마나 걸리는지 나타내는 성적표이다.

성능을 평사할때 실행시간을 기준으로 평가하기 어렵다.

돌아가는 환경(하드웨어 성능, 프로그래밍 언어, 입력 데이터 등)에 따라 달라지기 때문이다.

 

그렇기 때문에 복잡도라는 개념으로 입력 데이터를 n이라고 할때 데이터를 처리하는 절차가 몇 번인가를 나타낸다.

n이 증가할 수록 그만큼 시간과 공간이 증가해야 한다.

복잡도는 공간 복잡도와 시간 복잡도로 나뉜다.

  • 공간 복잡도는 메모리의 효율성이며, 알고리즘 과정 중 얼마나 많은 공간을 차지하는 지를 나타낸다.
  • 시간 복잡도는 실행 시간의 효율성이며, 알고리즘 과정 중 데이터를 처리하는데 얼마나 걸리는지를 나타낸다.

 

하지만, 공간 복잡도는 시대가 발전할수록 메모리의 양은 이미 충분하기 때문에 사실상 시간 복잡도에 초점을 두고 알고리즘을 평가한다. 평가 기준은 최악의 경우를 대비해야 하므로 일반적으로 알고리즘 평가는 빅오(O) 표기법으로 사용한다.

 

또한, 빅오 표기법에서는 영향력이 없는 항과 상수 계수를 제거하고 실질적으로 가장 영향력이 있는 최고차항만으로 시간복잡도를 표현한다. 이처럼 원함수를 단순화하여 최고차항의 차수만을 고려하는 표기법을 점근식 표기법이라고 한다.

3n²+ 1 이라는 시간복잡도 함수가 존재할 경우, 
최고차항과 상수 계수를 제거하면 O(n²)로 나타낼수 있다.

 

O(1) < O(log[n]) < O(n) < O(n∗log[n]) < O(n²) < O(2ⁿ) < O(n!)
오른쪽으로 갈 수록 비효율적이다.

 

자료구조는 다양하지만, Collection 프레임워크의 연결 리스트(Linked List)에 대해 살펴보면,

조회 삽입 삭제
O(n) O(1) O(1)

처음부터 노드를 타고 가야하는 연결 리스트는 조회 시 O(n)만큼 걸린다.

삽입과 삭제는 앞뒤 연결 고리만 바꾸면 되기 때문에(순수 삽입과 삭제) 가장 빠르다.

 

Java Collections Framework (JCF)

자바 컬렉션 프레임워크의 장점

  • 인터페이스와 다형성을 이용한 객체지향적 설계를 통해 표준화되어 있기 때문에, 사용법을 익히기에도 편리하고 재사용성이 높다.
  • 데이터 구조 및 알고리즘의 고성능 구현을 제공하여 프로그램의 성능과 품질을 향상한다.
  • 관련 없는 API 간의 상호 운용성을 제공한다. (상위 인터페이스 타입으로 업캐스팅하여 사용)
  • 이미 구현되어 있는 API를 사용하면 되기에, 새롭게 만들고 익힐 시간이 줄어든다.
  • 소프트웨어 재사용을 촉진한다.
    만일 자바에서 지원하지 않는 새로운 자료구조가 필요하다면, 컬렉션들을 재활용하고 조합하여 새로운 알고리즘을 만들어 낼 수 있다.

자바 컬렉션 프레임워크 사용 시 주의할 점

자바 컬렉션 프레임워크는 오로지 객체만 저장할 수 있다.

원시타입인 int나 double 등은 적재할 수 없다. 따라서 원시타입은 wrapper 클래스를 사용하여 Integer나 Double로 박싱 하여 저장해야 한다. 또한 객체를 담는다는 것은 곧 주소를 담는 것이므로 Null도 저장이 가능하다.

// [불가능] 컴파일 에러 발생!
List<int> numbers = new ArrayList<>(); 

// [가능] Wrapper 클래스를 사용해야 함
List<Integer> numbers = new ArrayList<>();

int는 제네릭 안에 사용할 수 없다.

 

오토 방식과 오토 언박싱

개발자가 일일이 변환하지 않아도, 자바 컴파일러가 알아서 포장하고(Boxing) 뜯어준다(Unboxing).

 

  • Auto-boxing: int → Integer (넣을 때 자동 포장)
  • Auto-unboxing: Integer → int (꺼낼 때 자동 개봉)

 

간단한 예시

import java.util.ArrayList;
import java.util.List;

public class ListExample {
    public static void main(String[] args) {
        // int형 데이터를 담기 위해 Integer Wrapper 클래스 사용
        List<Integer> scores = new ArrayList<>();

        // 1. 오토 박싱 (Auto-boxing)
        // 실제로는 list.add(new Integer(95)); 처럼 동작함
        scores.add(95); 
        scores.add(100);
        scores.add(85);

        // 2. 오토 언박싱 (Auto-unboxing)
        // 객체(Integer)를 꺼내서 바로 기본형(int) 변수에 담음
        int firstScore = scores.get(0); 
        
        // 연산도 바로 가능 (객체 + 기본형 -> 기본형)
        int sum = 0;
        for (Integer s : scores) {
            sum += s; // 자동으로 int로 변환되어 더해짐
        }

        System.out.println("총점: " + sum); // 280
    }
}

 

add안에 int형인 95, 100, 85를 ArrayList에 담으려고 할 때, 오토 박싱으로 wrapper 클래스인 Integer로 변환된다.

반대로, int형으로 꺼내려고 하면 오토 언박싱을 통해 Integer 객체는 int형으로 변환된다.

 

또한, int형 변수인 sum에 0을 저장하고 덧셈을 진행할 경우, Integer는 오토 언박싱으로 int형으로 변환되어 더해진다.

 

전체 구조

자바의 컬렉션 프레임워크는 크게 두 가지뿌리로 나뉜다.

 

Collection 인터페이스

하나의 덩어리 데이터를 다룬다.(List, Set, Queue)
Collection 인터페이스 위인 최상위 인터페이스 Iterable은 요소들을 순회할 때 사용하는 이터레이터와 관련되어 있다.

업캐스팅(다형성)으로 다양한 종류의 컬렉션을 받아 자료를 삽입하거나 삭제, 탐색할 수 있다.

하지만, Set 인터페이스는 get을 지원하지 않으므로 Iterator를 활용하여 객체를 순회해야 한다.

 

Map 인터페이스

키와 값의 쌍으로 다룬다.
Map은 Iterable을 상속받지 않기 때문에 iterator는 Map 컬렉션에 구현되어 있지 않는다.

 

List 인터페이스

출처: https://catsbi.oopy.io/8f0f5192-3a06-405e-8076-dbc5ff9f2dfb

  • 종류로는 ArrayList, LinkedList, Vector, Stack이 있다.
  • 구현체가 기본적으로 배열기반이지만, LinkedList는 예외적으로 연결기반의 컬렉션이다.
  • 저장순서가 유지되는 컬렉션을 구현하는 데 사용되며, 같은 요소의 중복 저장을 허용한다.
  • 리스트는 자료형 크기가 고정이 아닌 데이터에 따라 동적으로 확장, 축소할 수 있다.
  • 요소 사이에 빈 공간을 허용하지 않기 때문에 삽입/삭제할 때마다 배열 이동이 일어난다.

 

ArrayList

배열을 이용하여 만든 리스트로 데이터의 저장순서가 유지되고 중복을 허용한다.

동적으로 메모리가 자동으로 늘어나거나 줄어든다.

단방향 포인터 구조로 자료에 대한 순차적인 접근에 강점이 있어 조회가 빠르다.

순차적으로 추가와 삭제는 빠르지만, 중간에 삽입과 삭제는 느리다.

 

빅오 표기법을 확인하면, 조회가 대부분 사용할 경우 추천한다.

조회 삽입 삭제
O(1) O(n) O(n)

 

LinkedList

노드를 연결하여 리스트처럼 만든 컬렉션으로 List 인터페이스에서 유일하게 배열이 아니다.

데이터의 중간 삽입, 삭제가 빈번할 경우 빠른 성능을 보장한다.

하지만, 임의의 요소를 접근하려면, 순차적으로 검색해야 하므로 검색 성능은 좋지 않다.

자바의 LinkedList는 Doubly LinkedList(양방향 포인터 구조)로 이루어져 있다.

  • 평범한 LinkedList는 이동방향이 단방향이기에 이전 요소에 대한 접근이 어렵다.
  • Doubly LinkedList는 노드에 다음 노드의 주소뿐만이 아니라 이전노드의 주소도 가지고 있기 때문에 이전 요소로 접근 가능하다.

빅오 표기법을 확인하면, 삽입과 삭제가 빠른 것으로 확인할 수 있다.

조회 삽입 삭제
O(n) O(1) O(1)

 

Vector

ArrayList의 구형 버전으로 ArrayList와 달리 모든 메서드가 동기화되어 있다.

구버전 자바와 호환성을 위해 남겨둔 편이며, 잘 사용하지 않는다.

 

Stack

후입선출 LIFO 자료구조로 마지막에 들어온 원소가 처음으로 나간다.

들어올 때는 push, 나갈 때는 pop라는 용어를 사용한다.

 

 

Set 인터페이스

출처: https://catsbi.oopy.io/8f0f5192-3a06-405e-8076-dbc5ff9f2dfb

  • 데이터의 중복을 허용하지 않고 순서를 유지하지 않는 데이터의 집합 리스트이다.
  • 순서 자체가 없으므로 인덱스로 객체를 검색해서 가져오는 get() 메서드가 존재하지 않는다. -> Iterator 사용
  • 중복 저장이 불가능하기 때문에 null 값도 하나만 저장 가능하다.

 

HashSet

해싱 알고리즘을 이용해 데이터를 관리한다.

압도적인 속도, 데이터가 수백만 개여도 검색 속도가 거의 일정하다. 하지만, 순서를 전혀 예측할 수 없다.

조회 삽입
O(1) O(1) O(1)

 

LinkedHashSet

HashSet에 순서를 추가한 것으로 중복은 제거하고 싶은데, 들어온 순서대로 데이터를 다시 꺼내고 싶을 때 사용한다.

순서를 추가하기 위해 주소값을 가지고 있지만, 링크를 관리하므로 메모리를 조금 더 사용한다.

조회 삽입 삭제
O(1) O(1) O(1)

 

TreeSet

이진 검색 트리 자료구조의 형태로 데이터를 저장한다.

데이터를 넣기만 하면 자동으로 ㄱ - ㅎ, A - Z, 1 - 9 순으로 정렬된다.

범위 검색에 유리하다.

데이터를 넣을 때마다 정렬 위치를 찾아야 하므로 HashSet보다 느리다.

조회 삽입 삭제
O(log n) O(log n) O(log n)

 

Map 인터페이스

출처: https://catsbi.oopy.io/8f0f5192-3a06-405e-8076-dbc5ff9f2dfb

  • 키(key)와 값(value)의 쌍으로 연관 지어 이루어진 데이터의 집합이다.
  • 키는 Map에서 고유로 해당해야 하지만, 값은 중복으로 저장이 가능하다.
    만일, 기존에 저장된 데이터와 중복된 키와 값을 저장하면 기존의 값은 없어지고 마지막에 저장된 값이 남게 된다.
  • 저장 순서가 유지되지 않는다.

 

HashMap

HashTable을 보완한 컬렉션이며, 배열과 연결이 결합된 Hashing형태로, 키(key)와 값(value)을 묶어 하나의 데이터로 저장한다.

중복을 허용하지 않고 순서를 보장하지 않으며, 키와 값은 null이 허용된다.

추가, 삭제, 검색 모두 뛰어나다.

HashMap은 비동기로 작동하기 때문에 멀티 스레드 환경에서는 어울리지 않는다. (대신 ConcurrentHashMap 사용)

Key로 쓰는 객체는 반드시 hashCode()와 equals()가 잘 정의되어 있어야 한다.

조회 삽입 삭제
O(1) O(1) O(1)

 

LinkedHashMap

HashMap을 상속하기 때문에 흡사하지만, Entry들이 연결 리스트를 구성하여 데이터의 순서를 보장한다.

put 한 순서대로 데이터를 순회할 수 있으며, LRU 캐시를 만들 때 유용하다.

순서 관리 오버헤드가 존재한다.

조회 삽입 삭제
O(1) O(1) O(1)

 

TreeMap

이진 검색 트리의 형태로 키와 값의 쌍으로 이루어진 데이터를 저장한다.

SortedMap 인터페이스를 구현하고 있기에 Key 값을 기준으로 정렬한다.

정렬된 키와 값을 저장하므로 빠른 검색이 가능하다.

하지만, 키와 값을 저장하는 동시에 정렬을 행하기 때문에 저장시간이 다소 오래 걸린다.(HashMap 보다 느리다.)

조회 삽입 삭제
O(log n) O(log n) O(log n)

 

HashTable

옛날 자바에서 사용했으며, 동기화가 기본 지원되어 있어 Thread-Safe 하지만, 매우 느리다.

키와 값으로 null 허용하지 않는다.

 

실무 적용 방법

 

  • 그냥 리스트 말고 뭘 써야 할지 모르겠다면?
    • 일단 HashSet (중복 제거용) 또는 HashMap (데이터 매핑용)을 쓰세요. 90%는 이걸로 해결된다.
  • 순서가 중요한가?
    • 중요하면 LinkedHashSet / LinkedHashMap을 고려해 보기
    • 정렬(ㄱ-ㅎ)이 필요하면 TreeSet / TreeMap을 고려해 보기
  • Key로 사용할 객체 주의사항
    • HashMap의 Key로 사용자가 만든 클래스(예: Person)를 쓴다면, 반드시 equals()와 hashCode()를 오버라이딩해야 합니다. 그렇지 않으면 같은 사람인데도 다른 열쇠로 인식해서 값을 못 찾는 대참사가 발생한다.

 

면접 답변식 요약

컬렉션 프레임워크는 다수의 데이터를 쉽고 효과적으로 처리할 수 있도록 표준화된 클래스와 인터페이스의 집합입니다.

핵심 인터페이스로는 순서가 있고 중복을 허용하는 List, 순서가 없고 중복을 허용하지 않는 Set, 그리고 키와 값의 쌍으로 관리되는 Map이 있습니다.

실무에서는 데이터 조회 성능이 중요한 경우 ArrayListHashMap을 주로 사용하며, 중복 제거가 필요할 때는 HashSet을 사용하여 상황에 맞는 자료구조를 선택해 개발하고 있습니다.