[자바/심화] 컬렉션 프레임워크 (1/2)

컬렉션 프레임워크 (Collection Framework)

- 여러 가지 자료 구조(Data Structure)를 쉽게 사용할 수 있도록 자바에서 미리 구현하여 제공하는 클래스를 말한다.

- 대표적인 인터페이스로는 List 인터페이스, Set 인터페이스, Map 인터페이스 등이 있다.

 

Collection 계열 클래스

[Collection 계열 클래스의 계층 구조]
[Collection 계열 클래스의 계층 구조]

 

Collection 인터페이스에 선언되어 있는 주요 메소드들

- 모든 컬렉션 인터페이스에 공통적으로 존재하는 메소드이다.

- Collection 인터페이스를 상속하는 클래스들은 추상 메소드를 활용하여 다양한 방식으로 데이터를 관리할 수 있다.

추상 메소드 설명
  int size()   현재 컬렉션에 포함된 요소의 개수를 반환한다.
  boolean isEmpty()   현재 컬렉션이 비어 있는지 여부를 반환한다.
  boolean contains(Object o)   현재 컬렉션에 지정된 요소가 포함되어 있는 지 여부를 반환한다.
  boolean add(E e)   지정된 요소를 현재 컬렉션에 추가한다.
  boolean remove(Object o)   지정된 요소를 현재 컬렉션에서 제거한다.
  boolean addAll(Collection<? extends E> c)   지정된 컬렉션의 모든 요소를 현재 컬렉션에 추가한다.
  void clear()   현재 컬렉션의 모든 요소를 제거한다.
  boolean removeAll(Collection<?> c)   현재 컬렉션에서 지정된 컬렉션에 포함된 모든 요소를 제거한다.
  boolean retainAll(Collection<?> c)   현재 컬렉션에서 지정된 컬렉션에 포함된 요소만 남기고 다른 모든 요소를 제거한다.
  Iterator<E> iterator()   현재 컬렉션을 순회할 때 사용할 수 있는 Iterator 객체를 반환한다.
  Object[] toArray()   현재 컬렉션에 포함된 모든 요소를 배열로 반환한다.
  <T> T[] toArray(T[] a)   현재 컬렉션에 포함된 모든 요소를 지정된 배열에 저장하여 반환한다.

[JavaDoc에서 보기]

List 인터페이스 

- 배열과 비슷하게 동작하며, 데이터 저장 시 순서(Index)가 부여된다.

- 배열과의 가장 큰 차이점은 데이터 추가 시 자동으로 저장공간이 늘어난다.

- 데이터의 중복이 허용될 수 잇으며, 요소의 삽입 순서를 유지한다.

- List 인터페이스의 추상 메소드

추상 메소드 설명
  void add(int index, E element)   지정된 인덱스에 요소를 추가한다.
  boolean addAll(int index, Collection<? extends E> c)   지정된 인덱스에 컬렉션의 모든 요소를 추가한다.
  E remove(int index)   지정된 인덱스의 요소를 삭제하고 그 요소를 반환한다.
  E get(int index)   지정된 인덱스의 요소를 반환한다.
  E set(int index, E element)   지정된 인덱스의 요소를 새 요소로 대체하고 이전 요소를 반환한다.
  int indexOf(Object o)   지정된 요소의 첫 번째 인덱스를 반환합니다. 해당 요소가 없으면 -1을 반환한다.
  int lastIndexOf(Object o)   지정된 요소의 마지막 인덱스를 반환 한다.
  List<E> subList(int fromIndex, int toIndex)   지정된 범위의 요소로 구성된 부분 리스트를 반환한다.
  (fromIndex는 포함하고, toIndex는 포함하지 않음)
  void sort(Comparator<? super E> c)   지정된 Comparator에 따라 List를 정렬한다.
  Comparator를 제공하지 않으면 요소의 자연 순서에 따라 정렬한다.
  Iterator<E> iterator()   현재 컬렉션을 순회할 때 사용할 수 있는 Iterator 객체를 반환한다.
  Object[] toArray()   현재 컬렉션에 포함된 모든 요소를 배열로 반환한다.
  <T> T[] toArray(T[] a)   현재 컬렉션에 포함된 모든 요소를 지정된 배열에 저장하여 반환한다.

[JavaDoc에서 보기]

ArrayList 클래스

- List 계열의 클래스 중 가장 많이 사용되는 클래스이다.

- 객체 저장 시 Index가 순서대로 지정된다.

- 저장하는 객체의 개수에 제한이 없다.

- 동기화를 지원하지 않으며, 멀티스레드 환경에서 안정성을 보장할 수 없다.

- ArrayList 클래스를 사용한 List 인터페이스의 기능 구현 예시

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

public class Main {
    public static void main(String[] args) {
        // ArrayList 생성
        List<String> list = new ArrayList<>();

        // 요소 추가
        list.add("Apple");
        list.add("Banana");
        list.add("Orange");

        // 요소 접근과 출력
        System.out.println("List: " + list);

        // 특정 위치의 요소 가져오기
        String fruit = list.get(0);
        System.out.println("First fruit: " + fruit);

        // 요소 삭제
        list.remove("Orange");

        // 변경된 리스트 출력
        System.out.println("Modified List: " + list);

        // 리스트의 크기 출력
        System.out.println("Size of List: " + list.size());
    }
}

 

Vector 클래스

- 내부 메소드들이 동기화 되어 있으며, 이로 인해 멀티 스레드 환경에서 여러 스레드가 동시에 Vector 객체를 수정할 때 데이터의 일관성을 유지하는 데 도움이 된다.

- 동기화된 메소드 호출로 인하여 ArrayList 보다 성능이 떨어질 수 있다. 따라서, 단일 스레드 환경에서는 ArrayList를 사용하는 것이 성능상의 이점이 있을 수 있다.

- Vector 클래스 기능 구현 예시

import java.util.Vector;

public class Main {
    public static void main(String[] args) {
        // Vector 생성
        Vector<String> vector = new Vector<>();

        // 요소 추가
        vector.add("Apple");
        vector.add("Banana");
        vector.add("Orange");

        // 요소 접근과 출력
        System.out.println("Vector: " + vector);

        // Vector의 크기 출력
        System.out.println("Size of Vector: " + vector.size());
    }
}

 

LinkedList 클래스

- 이중 연결 리스트(doubly linked list) 기반의 구현체이다.

- 이중 연결 리스트는 각 노드가 이전 노드와 다음 노드의 참조를 가지고 있는 구조로, 요소들을 순차적으로 연결하여 저장한다.

- 요소의 추가나 삭제가 매우 빠르며, 특히 리스트의 시작이나 끝에 요소를 추가하거나 삭제할 때 ArrayList보다 유리할 수 있다.

- 인덱스로 요소를 찾는 경우 시작부터 해당 인덱스까지 순차적으로 접근해야 하므로 시간 복잡도는 O(n)으로 인덱스를 통한 요소 접근이 느리다.

- 데이터의 추가 삭제가 빈번한 경우 LinkedList를 사용하고, 검색 작업이 빈번한 경우 ArrayList를 사용하면 좋다.

- 주요 메소드

주요 메소드 설명
  void addFirst(E e)   리스트의 시작 부분에 요소를 추가한다.
  void addLast(E e)   리스트의 끝 부분에 요소를 추가한다.
  E getFirst()   리스트의 첫 번째 요소를 반환한다.
  E getLast()   리스트의 마지막 요소를 반환한다.
  E removeFirst()   리스트의 첫 번째 요소를 삭제하고 반환한다.
  E removeLast()   리스트의 마지막 요소를 삭제하고 반환한다.
  boolean offer(E e)   리스트의 끝에 요소를 추가한다.
  E poll()   리스트의 첫 번째 요소를 삭제하고 반환하며, 리스트가 비어 있으면 null을 반환한다.
  E peek()   리스트의 첫 번째 요소를 반환하며, 리스트가 비어 있으면 null을 반환한다.

[JavaDoc에서 보기]

- LinkedList 클래스 기능 구현 예시

import java.util.LinkedList;

public class Main {
    public static void main(String[] args) {
        // LinkedList 생성
        LinkedList<String> linkedList = new LinkedList<>();

        // 요소 추가
        linkedList.add("Apple");
        linkedList.add("Banana");
        linkedList.add("Orange");

        // 특정 위치의 요소 가져오기
        String fruit = linkedList.get(0);
        System.out.println("First fruit: " + fruit);

        // LinkedList의 크기 출력
        System.out.println("Size of LinkedList: " + linkedList.size());
    }
}

 

Queue 인터페이스 

- FIFO(First-In-First-Out) 방식의 데이터 구조를 구현하기 위한 인터페이스이다.

- 요소가 입력된 순서대로 저장되고, 요소는 처음에 입력된 것부터 순서대로 제거된다.

- 실제 큐의 기능은 java.util.Queue 인터페이스를 구현한 LinkedList 클래스 또는 PriorityQueue 클래스를 이용한다.

- LinkedList 클래스를 사용한 Queue 인터페이스의 기능 구현 예시

import java.util.LinkedList;
import java.util.Queue;

public class Main {
    public static void main(String[] args) {
        // Queue 인터페이스를 구현한 LinkedList 생성
        Queue<String> queue = new LinkedList<>();

        // 요소 추가
        queue.add("Apple");
        queue.add("Banana");
        queue.add("Orange");

        // 요소 제거
        String removed = queue.poll();
        System.out.println("Removed element: " + removed);

        // 큐의 크기 확인
        System.out.println("Queue size: " + queue.size());

        // 모든 요소 제거
        queue.clear();
        System.out.println("Queue cleared. Is empty? " + queue.isEmpty());
    }
}

 

Set 인터페이스 

- 동일한 요소가 중복되어 추가될 수 없으며, 이미 집합에 존재하는 요소들과 equality를 통해 중복 여부를 판단한다.

- 요소가 추가된 순서나 다른 기준에 따라 요소들이 정렬되지 않는다.

- 각 요소는 고유한 값을 가지므로, 동일한 객체를 다시 추가하려고 해도 추가되지 않는다.

- 대표적인 클래스로는 HashSet, TreeSet, LinkedHashSet 등이 있다.

- HashSet의 경우, 해시 테이블을 사용하여 요소를 저장하므로 검색 및 추가 연산이 평균적으로 빠르다.

- HashSet 클래스를 사용한 Set 인터페이스의 기능 구현 예시

import java.util.HashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        // HashSet 생성
        Set<String> set = new HashSet<>();

        // 요소 추가
        set.add("Apple");
        set.add("Banana");
        set.add("Orange");

        // 중복된 요소 추가 시도
        boolean added = set.add("Apple");
        System.out.println("Added 'Apple' again: " + added); // false 반환

        // 요소 포함 여부 확인
        boolean containsBanana = set.contains("Banana");
        System.out.println("Set contains 'Banana': " + containsBanana); // true 반환

        // 요소 삭제
        set.remove("Orange");

        // Set의 크기 확인
        System.out.println("Set size: " + set.size()); // 2 반환

        // 모든 요소 출력
        System.out.println("All elements in set: " + set); // 순서 없이 출력된다.
    }
}

 


⊙ 참고 문헌

  1. 이병승, 「초보 개발자를 위한 자바:한 권으로 배우는 자바 마스터 가이드 북」, 영진닷컴, 2024, p701 - 729
  2. 마종현, 「제로베이스 백엔드 취업 파트타임 스쿨 5기:Part 01. Java 기초-Chapter 01. Java 프로그래밍-컬렉션 프레임워크」, 제로베이스, 2024, https://zero-base.co.kr/ 
  3. Java™ Platform, Standard Edition 8 API Specification, 2024.07.23, https://docs.oracle.com/javase/8/docs/api/java/util/package-summary.html