컬렉션 프레임워크

ppp·2025년 7월 3일

Java 공부

목록 보기
8/13
post-thumbnail

컬렉션 프레임워크란 무엇인가?

프로그래밍을 하다 보면 데이터를 하나만 다루는 경우보다, 여러 개의 데이터를 그룹으로 묶어 처리해야 하는 상황이 훨씬 더 많습니다. Java에서는 이러한 다수의 데이터를 효과적으로 저장하고 관리할 수 있도록 컬렉션 프레임워크(Collection Framework)를 제공합니다.

컬렉션 + 프레임워크

  • 컬렉션(Collection): 다수의 데이터, 즉 데이터의 집합 또는 데이터 군을 의미합니다.

  • 프레임워크(Framework): 단순히 기능을 제공하는 데 그치지 않고, 표준화된 구조와 방식을 통해 개발 생산성과 유지보수성을 향상시키는 일종의 개발 틀입니다.

따라서 컬렉션 프레임워크란, 데이터를 그룹으로 저장하고 다루는 클래스들을 표준화된 설계 방식에 따라 구현한 구조라고 할 수 있습니다.

📌 공식 Java API 문서에서는 컬렉션 프레임워크를 다음과 같이 정의합니다.
"A unified architecture for representing and manipulating collections."
→ 즉, 데이터 군을 표현하고 처리하기 위한 통일된 아키텍처를 의미합니다.

컬렉션 프레임워크의 등장 배경

JDK 1.2 이전까지 Java에는 Vector, Hashtable, Properties 등 일부 데이터 집합을 다루는 클래스들이 존재했지만, 이들 간에 일관된 구조나 사용 방식이 없어 개발자 입장에서는 클래스마다 각기 다른 방식으로 데이터를 다뤄야 하는 불편함이 있었습니다.

그러나 JDK 1.2부터 컬렉션 프레임워크가 도입되면서 다음과 같은 변화가 이루어졌습니다.

  • 다양한 컬렉션 클래스(ArrayList, HashSet, HashMap 등)가 추가되었고,

  • 이들은 공통된 인터페이스를 기반으로 구현되어 일관된 방식으로 데이터 조작이 가능해졌습니다.

이러한 표준화는 생산성과 재사용성 향상이라는 큰 장점을 제공했지만, 완전한 성공이라고 보기는 어려웠습니다. 이후 JDK 1.8에 도입된 람다 표현식과 스트림(Stream) 기능이 결합되면서, 컬렉션 프레임워크는 진정한 의미의 표준화된 데이터 처리 방식을 완성하게 됩니다.

라이브러리와 프레임워크의 차이

구분설명
라이브러리유용한 기능을 묶어놓은 모듈. 필요할 때 호출하여 사용.
프레임워크단순한 기능 제공을 넘어, 전체적인 개발 방식과 구조를 정형화하여 생산성과 유지보수성을 향상시킴.

따라서, 컬렉션 프레임워크는 단순한 라이브러리의 개념을 넘어서 일관된 설계 철학을 가진 프로그램 구조라고 볼 수 있습니다.

컬렉션 프레임워크의 핵심 인터페이스

컬렉션 프레임워크는 설계적으로 인터페이스를 중심으로 구성되어 있으며, 다음의 세 가지 주요 인터페이스가 핵심입니다:

인터페이스주요 특징구현 클래스 예시
List순서가 있는 데이터의 집합
중복된 요소 허용
ArrayList, LinkedList, Vector, Stack
Set순서를 유지하지 않음
중복된 요소 허용하지 않음
HashSet, TreeSet, LinkedHashSet
Map키(key)와 값(value)의 쌍으로 구성
키는 중복 불가, 값은 중복 가능
HashMap, TreeMap, Hashtable, Properties

💡 추가로, List와 Set의 공통적인 기능을 묶어 Collection 인터페이스가 정의되어 있으며, Map은 Collection의 하위 인터페이스는 아닙니다.

클래스 이름으로 특징을 유추할 수 있다?

컬렉션 클래스들의 네이밍은 대체로 구현한 인터페이스의 이름을 포함하고 있기 때문에, 클래스 이름만 보더라도 해당 클래스가 어떤 특성을 가지는지 쉽게 파악할 수 있습니다.

예시:

ArrayList → 배열 기반의 List

HashSet → 해시 기반의 Set

TreeMap → 정렬 기능이 있는 Map

ArrayList vs LinkedList

Java 컬렉션 프레임워크에서 가장 많이 사용되는 List 구현체로는 ArrayList와 LinkedList가 있습니다. 두 클래스 모두 List 인터페이스를 구현하고 있지만, 내부 구조와 성능 특성에서 뚜렷한 차이를 가지고 있어, 상황에 따라 적절한 선택이 중요합니다.

ArrayList: 배열 기반의 리스트

  • ArrayList는 기존의 Vector를 개선한 클래스입니다. Vector와 구현 원리나 동작 방식은 유사하지만, 동기화를 지원하지 않아 성능이 더 우수합니다.

  • Java에서는 호환성을 위해 Vector를 여전히 유지하고 있지만, 새로운 코드에서는 일반적으로 ArrayList를 사용하는 것이 권장됩니다.

  • 내부적으로는 Object 배열을 사용하여 데이터를 순차적으로 저장합니다. 배열의 공간이 부족할 경우, 더 큰 배열을 새로 생성하고 기존 데이터를 복사한 후 추가 작업을 수행합니다.

public class ArrayList extends AbstractList
        implements List, RandomAccess, Cloneable, java.io.Serializable {
    ...
    transient Object[] elementData;
    ...
}
  • elementData는 Object 타입 배열로, Java의 모든 객체를 저장할 수 있습니다.

요소 추가 및 삭제의 동작 방식

  • 삭제 시: 삭제된 요소 아래의 모든 데이터를 한 칸씩 앞으로 이동시킵니다. 단, 마지막 요소 삭제 시에는 단순히 해당 요소를 null로 설정하면 됩니다.

  • 추가 시: 중간 위치에 요소를 삽입하는 경우, 이후의 요소들을 한 칸씩 뒤로 이동시킨 후 값을 삽입합니다.

📌 배열의 구조상, 맨 끝에서 추가/삭제하는 경우에는 데이터 이동이 필요 없기 때문에 성능이 상대적으로 우수합니다. 반대로, 중간 위치에서 삽입/삭제가 빈번한 경우에는 성능 저하가 발생할 수 있습니다.

LinkedList: 연결 기반의 리스트

  • LinkedList는 배열의 단점을 보완하기 위해 고안된 연결 리스트 기반의 자료구조입니다.

  • 배열은 메모리 상에 데이터가 연속적으로 존재하지만, 링크드 리스트는 불연속적인 메모리 공간에 존재하는 노드들이 포인터(주소값)를 통해 연결된 구조입니다.

  • 각 노드(Node)는 다음 요소의 주소와 데이터를 함께 가지고 있습니다.

class Node {
    Node next;   // 다음 노드의 참조
    Object obj;  // 실제 데이터
}
항목ArrayListLinkedList
내부 구조Object 배열노드(Node) 연결
데이터 접근 속도빠름 (인덱스를 통한 접근)느림 (순차 탐색 필요)
중간 삽입/삭제느림 (데이터 이동 필요)빠름 (포인터만 수정)
메모리 사용상대적으로 적음포인터 정보로 인해 더 큼
랜덤 액세스지원 (인덱스로 접근 가능)지원하지 않음

컬렉션을 순회하는 세 가지 방식: Iterator, ListIterator, Enumeration

Java 컬렉션 프레임워크는 데이터를 저장하는 것뿐 아니라, 저장된 데이터를 어떻게 효율적이고 일관된 방식으로 순회(iterate)할 수 있을지에 대해서도 깊이 있는 설계를 제공합니다. 이러한 순회 기능은 Iterator, ListIterator, Enumeration이라는 세 가지 주요 인터페이스를 통해 구현됩니다.

왜 Iterator가 필요한가?

컬렉션에 저장된 요소들을 순차적으로 읽어오는 기능은 거의 모든 프로그램에서 반복적으로 사용됩니다. Java는 이러한 작업을 표준화하기 위해 Iterator 인터페이스를 정의하고, 이를 통해 컬렉션 순회를 위한 통일된 프로그래밍 방식을 제공합니다.

public interface Iterator {
    boolean hasNext();   // 다음 요소가 있는지 확인
    Object next();       // 다음 요소 반환
    void remove();       // 현재 요소 삭제 (선택적 기능)
}

모든 Collection 계열 클래스(List, Set 등)는 iterator() 메서드를 통해 Iterator 객체를 반환합니다. 즉, 모든 컬렉션 클래스는 공통된 방식으로 순회가 가능합니다.

List list = new ArrayList();
Iterator it = list.iterator();

while(it.hasNext()) {
    System.out.println(it.next());
}

이러한 공통된 순회 방식을 통해 코드의 일관성, 재사용성, 유지보수성을 높일 수 있다는 점은 객체지향 설계에서 매우 중요한 가치입니다.

ListIterator

ListIterator는 Iterator의 기능을 확장한 인터페이스로, 다음과 같은 추가 기능을 제공합니다:

  • 양방향 탐색 (hasPrevious(), previous())

  • 요소의 인덱스 조회

  • 요소의 수정, 추가 (set(), add())

public interface ListIterator extends Iterator {
    boolean hasPrevious();
    Object previous();
    int nextIndex();
    int previousIndex();
    void set(Object e);
    void add(Object e);
}

단, ListIterator는 List 인터페이스를 구현한 컬렉션(ArrayList, LinkedList 등)에서만 사용할 수 있습니다. Set과 같은 비순차 컬렉션에서는 사용할 수 없습니다.

Enumeration

  • Enumeration은 Iterator의 구버전으로, 주로 Vector, Hashtable 같은 JDK 1.0 시절의 레거시 컬렉션 클래스에서 사용됩니다.

  • 기능은 hasMoreElements()와 nextElement() 정도로 제한적이며, 삭제 기능(remove)이 없는 등 유연성이 부족합니다.

  • 가능하면 Iterator를 사용할 것이 권장됩니다.

Enumeration e = vector.elements();
while(e.hasMoreElements()) {
    System.out.println(e.nextElement());
}

Comparable vs Comparator

Java 컬렉션 프레임워크에서는 객체 간의 정렬이 필요한 경우가 자주 발생합니다. 예를 들어, 이름순으로 학생 목록을 정렬하거나, 점수순으로 랭킹을 정렬하는 상황이 대표적입니다. 이러한 사용자 정의 객체의 정렬 기준을 명확히 지정하기 위해 Java는 두 가지 인터페이스를 제공합니다.

  • Comparable: 객체에 기본 정렬 기준을 정의

  • Comparator: 객체의 기본 정렬과 다른 기준을 동적으로 지정

구분ComparableComparator
목적객체 자체에 기본 정렬 기준 부여외부에서 정렬 기준 지정
패키지java.langjava.util
주요 메서드compareTo(T o)compare(T o1, T o2)
구현 위치정렬 대상 클래스 내부에 구현별도의 클래스로 구현
사용 예Arrays.sort(arr)Arrays.sort(arr, comparator)

인터페이스 정의

public interface Comparable<T> {
    int compareTo(T o); // this와 o를 비교
}

public interface Comparator<T> {
    int compare(T o1, T o2); // o1과 o2를 비교
}

각 메서드는 두 객체의 상대적 크기를 판단하여 다음의 값을 반환해야 합니다:

  • 0: 두 객체가 동일함

  • 양수: 왼쪽 객체가 더 큼

  • 음수: 오른쪽 객체가 더 큼

🔍 예제: 문자열 배열 정렬하기

import java.util.*;

public class Ex11_7 {
    public static void main(String[] args) {
        String[] strArr = { "cat", "Dog", "lion", "tiger" };

        Arrays.sort(strArr); // 기본 정렬: Comparable(String 클래스 내부 구현)
        System.out.println("기본 정렬: " + Arrays.toString(strArr));

        Arrays.sort(strArr, String.CASE_INSENSITIVE_ORDER); // 대소문자 무시
        System.out.println("대소문자 무시: " + Arrays.toString(strArr));

        Arrays.sort(strArr, new Descending()); // 역순 정렬
        System.out.println("역순 정렬: " + Arrays.toString(strArr));
    }
}

class Descending implements Comparator {
    public int compare(Object o1, Object o2) {
        if (o1 instanceof Comparable && o2 instanceof Comparable) {
            Comparable c1 = (Comparable) o1;
            return c1.compareTo(o2) * -1; // 기본 정렬 기준의 반대 (역순)
        }
        return -1;
    }
}

위 예제에서 String 클래스는 이미 Comparable을 구현하고 있기 때문에 Arrays.sort()만으로도 기본 정렬이 가능합니다. Comparator는 이 기본 정렬 기준을 변경하고 싶을 때 사용됩니다.

0개의 댓글