본문 바로가기
이직&취업/Java 기초 상식

Java Collection Framework란?

by journeylabs 2025. 3. 23.
728x90
반응형

목차

    Java Collection Framework란?

    Java Collection Framework는 Java 프로그래밍에서 데이터를 효율적으로 저장, 관리, 조작하기 위한 핵심적인 도구입니다. 이 글에서는 Collection Framework의 용어 설명부터 주요 개념, 상속 관계, 활용 예제, 그리고 주의사항까지 꼼꼼하게 다루어 Java 개발 능력을 한 단계 끌어올리는 데 도움을 드리고자 합니다.

    1. 용어 상세 설명

    • 컬렉션(Collection): 여러 데이터(객체)를 하나의 그룹으로 묶어 관리하는 자료구조를 의미합니다. Java Collection Framework는 다양한 종류의 컬렉션을 제공하며, 각 컬렉션은 특정 목적과 성능 요구사항에 맞춰 설계되었습니다.
    • 프레임워크(Framework): 특정 작업을 수행하기 위해 미리 정의된 클래스와 인터페이스의 집합입니다. Collection Framework는 컬렉션을 다루기 위한 표준화된 인터페이스와 구현체를 제공하여 개발자가 일관성 있게 코드를 작성하고 유지보수할 수 있도록 돕습니다.
    • 인터페이스(Interface): 클래스가 구현해야 하는 메서드들의 목록을 정의합니다. Collection Framework는 Collection, List, Set, Map 등의 핵심 인터페이스를 제공하며, 각 인터페이스는 특정 컬렉션의 동작 방식을 규정합니다.
    • 구현체(Implementation): 인터페이스를 실제로 구현한 클래스입니다. 예를 들어, List 인터페이스의 구현체로는 ArrayList, LinkedList 등이 있습니다.
    • 제네릭(Generics): 컬렉션에 저장할 수 있는 객체의 타입을 지정하는 기능입니다. 제네릭을 사용하면 컴파일 시점에 타입 오류를 검출할 수 있어 안정적인 코드를 작성할 수 있습니다. 예를 들어, List<String>은 문자열만 저장할 수 있는 리스트를 의미합니다.

    2. 주요 개념 및 특징

    • 자료구조의 다양성: Collection Framework는 List, Set, Map 등 다양한 자료구조를 제공합니다. 각 자료구조는 데이터 저장 방식과 접근 방식이 다르므로, 문제 해결에 적합한 자료구조를 선택하는 것이 중요합니다.
    • 인터페이스 기반 설계: Collection Framework는 인터페이스를 중심으로 설계되어 유연성과 확장성이 뛰어납니다. 개발자는 특정 구현체에 종속되지 않고 인터페이스를 사용하여 코드를 작성할 수 있으며, 필요에 따라 구현체를 쉽게 변경할 수 있습니다.
    • 제네릭 타입 지원: Collection Framework는 제네릭 타입을 지원하여 타입 안정성을 높이고 코드의 가독성을 향상시킵니다.
    • Iterator를 통한 순회: Collection의 모든 요소에 접근하기 위해 Iterator 인터페이스를 제공합니다. Iterator를 사용하면 컬렉션의 내부 구조에 상관없이 일관된 방식으로 요소에 접근할 수 있습니다.
    • 다양한 알고리즘 제공: Collections 클래스는 컬렉션을 정렬, 검색, 복사하는 등 다양한 알고리즘을 제공합니다. 이러한 알고리즘을 활용하면 개발자는 직접 코드를 작성하지 않고도 효율적으로 컬렉션을 조작할 수 있습니다.

    3. 상속 관계

    Collection Framework의 핵심 인터페이스와 클래스 간의 상속 관계는 다음과 같습니다.

                                    Iterable
                                       |
                                  Collection
                                       |
                        +--------------+---------------+
                        |              |               |
                       List           Set             Queue
                        |              |               |
            +-----------+-----------+  +-----------+   |
            |           |           |  |           |   |
        ArrayList LinkedList Vector  HashSet TreeSet PriorityQueue
            |                          |
          Stack                   LinkedHashSet
    
                                   	  Map
                                       |
                        +--------------+---------------+
                        |              |               |
                    HashMap         TreeMap       LinkedHashMap
                        |
                    Hashtable
    • Iterable: Iterator를 반환하는 iterator() 메서드를 정의합니다. Collection 인터페이스는 Iterable 인터페이스를 상속받아 모든 컬렉션이 반복 작업을 지원하도록 합니다.
    • Collection: 컬렉션의 기본적인 동작을 정의합니다. add(), remove(), contains(), size() 등의 메서드를 제공합니다.
    • List: 순서가 있는 컬렉션으로, 요소의 중복을 허용합니다. ArrayList, LinkedList, Vector 등이 List 인터페이스를 구현합니다.
      • ArrayList: 배열 기반의 리스트로, 요소에 빠르게 접근할 수 있습니다.
      • LinkedList: 연결 리스트 기반의 리스트로, 요소의 삽입/삭제가 빠릅니다.
      • Vector: ArrayList와 유사하지만, 스레드 동기화를 지원합니다.
      • Stack: LIFO(Last-In-First-Out) 방식으로 요소를 관리하는 스택 자료구조를 구현합니다.
    • Set: 중복된 요소를 허용하지 않는 컬렉션입니다. HashSet, TreeSet 등이 Set 인터페이스를 구현합니다.
      • HashSet: 해시 테이블 기반의 Set으로, 요소의 순서를 보장하지 않습니다.
      • TreeSet: 트리 기반의 Set으로, 요소를 정렬된 상태로 유지합니다.
      • LinkedHashSet: HashSet과 유사하지만, 요소의 삽입 순서를 유지합니다.
    • Queue: FIFO(First-In-First-Out) 방식으로 요소를 관리하는 큐 자료구조를 정의합니다.
      • PriorityQueue: 우선순위 큐를 구현하며, 요소의 우선순위에 따라 순서가 결정됩니다.
    • Map: 키(Key)와 값(Value)의 쌍으로 이루어진 데이터를 저장하는 컬렉션입니다. 키는 중복될 수 없으며, 각 키는 하나의 값에 매핑됩니다. HashMap, TreeMap, LinkedHashMap 등이 Map 인터페이스를 구현합니다.
      • HashMap: 해시 테이블 기반의 Map으로, 키의 순서를 보장하지 않습니다.
      • TreeMap: 트리 기반의 Map으로, 키를 정렬된 상태로 유지합니다.
      • LinkedHashMap: HashMap과 유사하지만, 키의 삽입 순서를 유지합니다.
      • Hashtable: HashMap과 유사하지만, 스레드 동기화를 지원합니다.

    4. 예제 및 예제 상세 설명

    4.1. List 인터페이스 구현체

    4.1.1. ArrayList: 동적 배열 기반의 리스트

    • 특징:
      • 내부적으로 배열을 사용하여 요소를 저장합니다.
      • 요소에 대한 임의 접근(random access)이 빠릅니다 (O(1)).
      • 요소의 삽입/삭제 시 배열의 복사가 발생할 수 있어 성능 저하가 발생할 수 있습니다 (O(n)). 특히, 중간 위치에 삽입/삭제하는 경우 성능에 큰 영향을 미칩니다.
      • Thread-safe하지 않습니다.
    • 활용: 요소에 대한 빠른 접근이 필요한 경우, 데이터의 크기가 비교적 작고 삽입/삭제가 드물게 발생하는 경우에 적합합니다.
    import java.util.ArrayList;
    import java.util.List;
    
    public class ArrayListExample {
        public static void main(String[] args) {
            // ArrayList 생성
            List<String> names = new ArrayList<>();
    
            // 요소 추가
            names.add("Alice");
            names.add("Bob");
            names.add("Charlie");
    
            // 특정 인덱스에 요소 추가
            names.add(1, "David"); // "Alice", "David", "Bob", "Charlie"
    
            // 요소 접근
            System.out.println(names.get(2)); // Bob 출력
    
            // 요소 삭제
            names.remove("Bob"); // "Alice", "David", "Charlie"
    
            // 요소 수정
            names.set(1, "Eve"); // "Alice", "Eve", "Charlie"
    
            // 반복문을 이용한 요소 순회
            for (String name : names) {
                System.out.println(name);
            }
        }
    }
    • ArrayList<String> names = new ArrayList<>();: 문자열을 저장할 ArrayList를 생성합니다.
    • names.add(1, "David");: 인덱스 1에 "David"를 삽입합니다. 기존 요소들은 뒤로 밀려납니다.
    • names.set(1, "Eve");: 인덱스 1의 요소를 "Eve"로 변경합니다.

    4.1.2. LinkedList: 연결 리스트 기반의 리스트

    • 특징:
      • 각 요소가 이전 요소와 다음 요소의 주소를 저장하는 방식으로 연결되어 있습니다.
      • 요소의 삽입/삭제가 빠릅니다 (O(1)). ArrayList와 달리 배열 복사가 필요하지 않습니다.
      • 요소에 대한 임의 접근이 느립니다 (O(n)). 특정 인덱스의 요소에 접근하려면 처음부터 순차적으로 탐색해야 합니다.
      • Thread-safe하지 않습니다.
    • 활용: 요소의 삽입/삭제가 빈번하게 발생하는 경우, 데이터의 크기가 크고 임의 접근이 드물게 발생하는 경우에 적합합니다. 큐(Queue)나 스택(Stack)을 구현하는 데에도 유용합니다.
    import java.util.LinkedList;
    import java.util.List;
    
    public class LinkedListExample {
        public static void main(String[] args) {
            // LinkedList 생성
            List<String> tasks = new LinkedList<>();
    
            // 요소 추가
            tasks.add("Task 1");
            tasks.add("Task 2");
    
            // List의 맨 앞에 요소 추가
            ((LinkedList<String>) tasks).addFirst("Task 0"); // "Task 0", "Task 1", "Task 2"
    
            // List의 맨 뒤에 요소 추가
            ((LinkedList<String>) tasks).addLast("Task 3"); // "Task 0", "Task 1", "Task 2", "Task 3"
    
            // 요소 접근
            System.out.println(tasks.get(1)); // Task 1 출력
    
            // 요소 삭제
            tasks.remove("Task 2"); // "Task 0", "Task 1", "Task 3"
    
            // 반복문을 이용한 요소 순회
            for (String task : tasks) {
                System.out.println(task);
            }
        }
    }
    • ((LinkedList<String>) tasks).addFirst("Task 0");: LinkedList의 맨 앞에 "Task 0"을 추가합니다.
    • ((LinkedList<String>) tasks).addLast("Task 3");: LinkedList의 맨 뒤에 "Task 3"을 추가합니다. LinkedList는 addFirst()addLast() 메서드를 제공하여 리스트의 시작과 끝에 요소를 쉽게 추가할 수 있습니다. 주의: LinkedList의 기능을 사용하려면 List 인터페이스로 선언된 변수를 LinkedList로 캐스팅해야 합니다.

    4.1.3. Vector: 동기화된 ArrayList

    • 특징:
      • ArrayList와 유사하지만, 모든 메서드가 synchronized 키워드로 동기화되어 있어 Thread-safe합니다.
      • 멀티스레드 환경에서 안전하게 사용할 수 있지만, 동기화로 인해 성능이 저하될 수 있습니다.
      • Legacy 클래스이므로, 특별한 이유가 없다면 ArrayList를 사용하고, 멀티스레드 환경에서는 Collections.synchronizedList() 메서드를 사용하여 Thread-safe한 리스트를 만드는 것이 좋습니다.
    • 활용: 멀티스레드 환경에서 안전하게 리스트를 공유해야 하는 경우에 사용할 수 있습니다. 하지만, 성능 저하를 고려해야 합니다.
    import java.util.List;
    import java.util.Vector;
    
    public class VectorExample {
        public static void main(String[] args) {
            // Vector 생성
            List<String> names = new Vector<>();
    
            // 요소 추가
            names.add("Alice");
            names.add("Bob");
    
            // 요소 접근
            System.out.println(names.get(0)); // Alice 출력
    
            // 요소 삭제
            names.remove("Bob");
    
            // 반복문을 이용한 요소 순회
            for (String name : names) {
                System.out.println(name);
            }
        }
    }

    4.1.4. Stack: LIFO (Last-In-First-Out) 스택

    • 특징:
      • Vector 클래스를 상속받아 구현되었습니다.
      • LIFO (Last-In-First-Out) 방식으로 요소를 관리합니다.
      • push(), pop(), peek() 등의 메서드를 제공합니다.
      • Vector와 마찬가지로 Thread-safe합니다.
    • 활용: 후입선출(LIFO) 방식의 자료구조가 필요한 경우 (예: 함수 호출 스택, 웹 브라우저 방문 기록)에 사용됩니다.
    import java.util.Stack;
    
    public class StackExample {
        public static void main(String[] args) {
            // Stack 생성
            Stack<Integer> stack = new Stack<>();
    
            // 요소 추가 (push)
            stack.push(1);
            stack.push(2);
            stack.push(3);
    
            // 최상위 요소 확인 (peek)
            System.out.println(stack.peek()); // 3 출력
    
            // 요소 삭제 (pop)
            System.out.println(stack.pop()); // 3 출력
            System.out.println(stack.pop()); // 2 출력
            System.out.println(stack.pop()); // 1 출력
    
            // 스택이 비어있는지 확인
            System.out.println(stack.isEmpty()); // true 출력
        }
    }
    • stack.push(1);: 스택에 정수 1을 push합니다.
    • stack.peek();: 스택의 최상위 요소(가장 최근에 push된 요소)를 반환합니다. 스택에서 요소를 제거하지 않습니다.
    • stack.pop();: 스택의 최상위 요소를 반환하고 스택에서 제거합니다.
    • stack.isEmpty();: 스택이 비어있는지 확인합니다.

    4.2. Set 인터페이스 구현체

    4.2.1. HashSet: 해시 테이블 기반의 Set

    • 특징:
      • 중복된 요소를 허용하지 않습니다.
      • 요소의 순서를 보장하지 않습니다.
      • add(), remove(), contains() 메서드의 시간 복잡도는 평균적으로 O(1)입니다.
      • null 요소를 하나만 허용합니다.
      • Thread-safe하지 않습니다.
    • 활용: 중복된 데이터를 제거해야 하는 경우, 요소의 순서가 중요하지 않은 경우에 적합합니다.
    import java.util.HashSet;
    import java.util.Set;
    
    public class HashSetExample {
        public static void main(String[] args) {
            // HashSet 생성
            Set<String> uniqueNames = new HashSet<>();
    
            // 요소 추가
            uniqueNames.add("Alice");
            uniqueNames.add("Bob");
            uniqueNames.add("Alice"); // 중복된 값은 추가되지 않음
    
            // 크기 확인
            System.out.println(uniqueNames.size()); // 2 출력
    
            // 요소 포함 여부 확인
            System.out.println(uniqueNames.contains("Bob")); // true 출력
    
            // 요소 삭제
            uniqueNames.remove("Bob");
    
            // 반복문을 이용한 요소 순회 (순서 보장 X)
            for (String name : uniqueNames) {
                System.out.println(name);
            }
        }
    }
    • uniqueNames.add("Alice");: HashSet에 "Alice"를 추가합니다. 이미 "Alice"가 존재하더라도 중복된 값은 추가되지 않습니다.

    4.2.2. TreeSet: 트리 기반의 정렬된 Set

    • 특징:
      • 중복된 요소를 허용하지 않습니다.
      • 요소를 정렬된 상태로 유지합니다 (기본적으로 오름차순).
      • 요소를 정렬하기 위해 Comparable 인터페이스를 구현하거나 Comparator를 제공해야 합니다.
      • add(), remove(), contains() 메서드의 시간 복잡도는 O(log n)입니다.
      • null 요소를 허용하지 않습니다.
      • Thread-safe하지 않습니다.
    • 활용: 요소를 정렬된 상태로 유지해야 하는 경우, 범위 검색(range query)이 필요한 경우에 적합합니다.
    import java.util.Set;
    import java.util.TreeSet;
    
    public class TreeSetExample {
        public static void main(String[] args) {
            // TreeSet 생성
            Set<Integer> sortedNumbers = new TreeSet<>();
    
            // 요소 추가
            sortedNumbers.add(3);
            sortedNumbers.add(1);
            sortedNumbers.add(2);
    
            // 반복문을 이용한 요소 순회 (정렬된 순서대로 출력)
            for (Integer number : sortedNumbers) {
                System.out.println(number); // 1, 2, 3 출력
            }
    
            // 사용자 정의 객체 정렬 (Comparable 인터페이스 구현 필요)
            Set<Person> people = new TreeSet<>();
            people.add(new Person("Bob", 30));
            people.add(new Person("Alice", 25));
    
            for (Person person : people) {
                System.out.println(person.name + ": " + person.age); // Alice: 25, Bob: 30 출력
            }
        }
    
        static class Person implements Comparable<Person> {
            String name;
            int age;
    
            public Person(String name, int age) {
                this.name = name;
                this.age = age;
            }
    
            @Override
            public int compareTo(Person other) {
                return this.age - other.age; // 나이 오름차순 정렬
            }
        }
    }
    • Set<Person> people = new TreeSet<>();: Person 객체를 저장할 TreeSet을 생성합니다. Person 클래스는 Comparable 인터페이스를 구현하여 나이를 기준으로 정렬하도록 정의되어 있습니다.
    • this.age - other.age;: compareTo() 메서드에서 나이의 차이를 반환하여 오름차순으로 정렬합니다.

    4.2.3. LinkedHashSet: 연결 리스트 기반의 HashSet

    • 특징:
      • HashSet과 유사하지만, 요소의 삽입 순서를 유지합니다.
      • HashSet보다 약간의 추가 메모리를 사용합니다.
      • Thread-safe하지 않습니다.
    • 활용: 요소의 삽입 순서가 중요한 경우, HashSet의 성능과 정렬된 순서 유지를 동시에 원하는 경우에 적합합니다.
    import java.util.LinkedHashSet;
    import java.util.Set;
    
    public class LinkedHashSetExample {
        public static void main(String[] args) {
            // LinkedHashSet 생성
            Set<String> orderedNames = new LinkedHashSet<>();
    
            // 요소 추가
            orderedNames.add("Alice");
            orderedNames.add("Bob");
            orderedNames.add("Charlie");
    
            // 반복문을 이용한 요소 순회 (삽입 순서대로 출력)
            for (String name : orderedNames) {
                System.out.println(name); // Alice, Bob, Charlie 출력
            }
        }
    }
    • orderedNames.add("Alice");: LinkedHashSet에 "Alice", "Bob", "Charlie"를 순서대로 추가합니다. LinkedHashSet은 삽입 순서를 유지하므로, 반복문을 사용하여 순회할 때 삽입 순서대로 요소가 출력됩니다.

    4.3. Queue 인터페이스 구현체

    4.3.1. PriorityQueue: 우선순위 큐

    • 특징:
      • 큐의 요소에 우선순위를 부여하여, 우선순위가 높은 요소부터 꺼냅니다.
      • 내부적으로 Heap 자료구조를 사용하여 구현됩니다.
      • 요소의 우선순위를 결정하기 위해 Comparable 인터페이스를 구현하거나 Comparator를 제공해야 합니다.
      • null 요소를 허용하지 않습니다.
      • Thread-safe하지 않습니다.
    • 활용: 작업 스케줄링, 최단 경로 알고리즘 등 우선순위 기반의 작업 처리가 필요한 경우에 사용됩니다.
    import java.util.PriorityQueue;
    import java.util.Queue;
    
    public class PriorityQueueExample {
        public static void main(String[] args) {
            // PriorityQueue 생성
            Queue<Integer> priorityQueue = new PriorityQueue<>(); // 낮은 숫자가 우선순위가 높음
    
            // 요소 추가
            priorityQueue.add(3);
            priorityQueue.add(1);
            priorityQueue.add(2);
    
            // 요소 삭제 (우선순위가 높은 요소부터 삭제)
            System.out.println(priorityQueue.poll()); // 1 출력
            System.out.println(priorityQueue.poll()); // 2 출력
            System.out.println(priorityQueue.poll()); // 3 출력
    
            // 사용자 정의 객체 우선순위 큐 (Comparator 인터페이스 구현 필요)
            Queue<Task> taskQueue = new PriorityQueue<>((t1, t2) -> t1.priority - t2.priority); // 우선순위가 낮은 숫자가 우선순위가 높음
            taskQueue.add(new Task("Task A", 3));
            taskQueue.add(new Task("Task B", 1));
            taskQueue.add(new Task("Task C", 2));
    
            while (!taskQueue.isEmpty()) {
                Task task = taskQueue.poll();
                System.out.println(task.name + ": " + task.priority); // Task B: 1, Task C: 2, Task A: 3 출력
            }
        }
    
        static class Task {
            String name;
            int priority;
    
            public Task(String name, int priority) {
                this.name = name;
                this.priority = priority;
            }
        }
    }
    • Queue<Task> taskQueue = new PriorityQueue<>((t1, t2) -> t1.priority - t2.priority);: Task 객체를 저장할 PriorityQueue를 생성합니다. 람다 표현식을 사용하여 Comparator를 정의하고, priority 필드를 기준으로 우선순위를 결정합니다. 숫자가 작을수록 우선순위가 높습니다.
    • taskQueue.poll();: 큐에서 우선순위가 가장 높은 요소를 꺼냅니다.

    4.4. Map 인터페이스 구현체

    4.4.1. HashMap: 해시 테이블 기반의 Map

    • 특징:
      • 키(Key)와 값(Value)의 쌍으로 이루어진 데이터를 저장합니다.
      • 키는 중복될 수 없으며, 각 키는 하나의 값에 매핑됩니다.
      • 키를 기준으로 데이터를 검색하므로, 검색 속도가 빠릅니다 (평균적으로 O(1)).
      • 요소의 순서를 보장하지 않습니다.
      • null 키를 하나만 허용하고, null 값을 여러 개 허용합니다.
      • Thread-safe하지 않습니다.
    • 활용: 키를 이용하여 빠르게 데이터를 검색해야 하는 경우에 적합합니다.
    import java.util.HashMap;
    import java.util.Map;
    
    public class HashMapExample {
        public static void main(String[] args) {
            // HashMap 생성
            Map<String, Integer> ages = new HashMap<>();
    
            // 요소 추가 (Key-Value 쌍)
            ages.put("Alice", 25);
            ages.put("Bob", 30);
            ages.put("Charlie", 35);
    
            // Key를 이용한 값 접근
            System.out.println(ages.get("Bob")); // 30 출력
    
            // Key 존재 여부 확인
            System.out.println(ages.containsKey("Alice")); // true 출력
    
            // Value 존재 여부 확인
            System.out.println(ages.containsValue(30)); // true 출력
    
            // 요소 삭제
            ages.remove("Bob");
    
            // KeySet을 이용한 순회
            for (String key : ages.keySet()) {
                System.out.println(key + ": " + ages.get(key));
            }
    
            // EntrySet을 이용한 순회
            for (Map.Entry<String, Integer> entry : ages.entrySet()) {
                System.out.println(entry.getKey() + ": " + entry.getValue());
            }
        }
    }
    • ages.keySet(): Map에 저장된 모든 키를 Set 형태로 반환합니다.
    • ages.entrySet(): Map에 저장된 모든 키-값 쌍을 Map.Entry 형태로 반환합니다.

    4.4.2. TreeMap: 트리 기반의 정렬된 Map

    • 특징:
      • 키를 정렬된 상태로 유지합니다 (기본적으로 오름차순).
      • 키를 기준으로 데이터를 검색하므로, 검색 속도가 빠릅니다 (O(log n)).
      • 키를 정렬하기 위해 Comparable 인터페이스를 구현하거나 Comparator를 제공해야 합니다.
      • null 키를 허용하지 않습니다.
      • Thread-safe하지 않습니다.
    • 활용: 키를 정렬된 상태로 유지해야 하는 경우, 범위 검색(range query)이 필요한 경우에 적합합니다.
    import java.util.Map;
    import java.util.TreeMap;
    
    public class TreeMapExample {
        public static void main(String[] args) {
            // TreeMap 생성
            Map<String, Integer> sortedAges = new TreeMap<>();
    
            // 요소 추가
            sortedAges.put("Charlie", 35);
            sortedAges.put("Alice", 25);
            sortedAges.put("Bob", 30);
    
            // 반복문을 이용한 요소 순회 (키를 기준으로 정렬된 순서대로 출력)
            for (String key : sortedAges.keySet()) {
                System.out.println(key + ": " + sortedAges.get(key)); // Alice: 25, Bob: 30, Charlie: 35 출력
            }
        }
    }
    • TreeMap은 키를 기준으로 정렬된 순서대로 요소를 저장하므로, 반복문을 사용하여 순회할 때 키가 알파벳 순서대로 출력됩니다.

    4.4.3. LinkedHashMap: 연결 리스트 기반의 HashMap

    • 특징:
      • HashMap과 유사하지만, 키의 삽입 순서를 유지합니다.
      • HashMap보다 약간의 추가 메모리를 사용합니다.
      • Thread-safe하지 않습니다.
    • 활용: 키의 삽입 순서가 중요한 경우, HashMap의 성능과 삽입 순서 유지를 동시에 원하는 경우에 적합합니다.
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    public class LinkedHashMapExample {
        public static void main(String[] args) {
            // LinkedHashMap 생성
            Map<String, Integer> orderedAges = new LinkedHashMap<>();
    
            // 요소 추가
            orderedAges.put("Charlie", 35);
            orderedAges.put("Alice", 25);
            orderedAges.put("Bob", 30);
    
            // 반복문을 이용한 요소 순회 (삽입 순서대로 출력)
            for (String key : orderedAges.keySet()) {
                System.out.println(key + ": " + orderedAges.get(key)); // Charlie: 35, Alice: 25, Bob: 30 출력
            }
        }
    }
    • LinkedHashMap은 키의 삽입 순서를 유지하므로, 반복문을 사용하여 순회할 때 키가 삽입 순서대로 출력됩니다.

    4.4.4. Hashtable: 동기화된 HashMap

    • 특징:
      • HashMap과 유사하지만, 모든 메서드가 synchronized 키워드로 동기화되어 있어 Thread-safe합니다.
      • 멀티스레드 환경에서 안전하게 사용할 수 있지만, 동기화로 인해 성능이 저하될 수 있습니다.
      • null 키와 null 값을 허용하지 않습니다.
      • Legacy 클래스이므로, 특별한 이유가 없다면 HashMap을 사용하고, 멀티스레드 환경에서는 ConcurrentHashMap을 사용하는 것이 좋습니다.
    • 활용: 멀티스레드 환경에서 안전하게 Map을 공유해야 하는 경우에 사용할 수 있습니다. 하지만, 성능 저하를 고려해야 합니다.
    import java.util.Hashtable;
    import java.util.Map;
    
    public class HashtableExample {
        public static void main(String[] args) {
            // Hashtable 생성
            Map<String, Integer> ages = new Hashtable<>();
    
            // 요소 추가
            ages.put("Alice", 25);
            ages.put("Bob", 30);
    
            // 요소 접근
            System.out.println(ages.get("Alice")); // 25 출력
    
            // 요소 삭제
            ages.remove("Bob");
    
            // 반복문을 이용한 요소 순회
            for (String key : ages.keySet()) {
                System.out.println(key + ": " + ages.get(key));
            }
        }
    }

    핵심 정리:

    클래스 특징 활용 Thread-safe null 허용
    ArrayList 배열 기반, 빠른 임의 접근, 삽입/삭제 느림 요소 접근 빈도가 높고, 삽입/삭제 빈도가 낮은 경우 X O
    LinkedList 연결 리스트 기반, 빠른 삽입/삭제, 임의 접근 느림 요소 삽입/삭제 빈도가 높고, 임의 접근 빈도가 낮은 경우, 큐/스택 구현 X O
    Vector ArrayList와 유사, 동기화 지원 멀티스레드 환경에서 안전하게 리스트를 공유해야 하는 경우 (성능 고려) O O
    Stack Vector 상속, LIFO 후입선출(LIFO) 방식의 자료구조가 필요한 경우 O O
    HashSet 해시 테이블 기반, 중복 불허, 순서 보장 X 중복 제거, 순서가 중요하지 않은 경우 X O (1개)
    TreeSet 트리 기반, 중복 불허, 정렬된 순서 유지 정렬된 데이터 필요, 범위 검색 X X
    LinkedHashSet 연결 리스트 + 해시 테이블, 중복 불허, 삽입 순서 유지 삽입 순서 유지 필요, HashSet의 성능 X O (1개)
    PriorityQueue Heap 기반, 우선순위 큐 작업 스케줄링, 최단 경로 알고리즘 X X
    HashMap 해시 테이블 기반, Key-Value, 순서 보장 X 빠른 검색 X O (Key 1개)
    TreeMap 트리 기반, Key-Value, 키 정렬 정렬된 키 필요, 범위 검색 X X
    LinkedHashMap 연결 리스트 + 해시 테이블, Key-Value, 삽입 순서 유지 삽입 순서 유지 필요, HashMap의 성능 X O (Key 1개)
    Hashtable HashMap과 유사, 동기화 지원, null 불허 멀티스레드 환경에서 안전하게 Map을 공유해야 하는 경우 (성능 고려) O X
    • Thread-safe: 멀티스레드 환경에서 안전하게 사용할 수 있는지 여부
    • null 허용: null 값을 요소로 허용하는지 여부 (List, Set, Map에 따라 null 키 또는 null 값 허용 여부가 다름)
    • Key 1개: HashMap, LinkedHashMap은 null 키를 1개만 허용
    • O (1개): HashSet, LinkedHashSet은 null 요소를 1개만 허용

    5. 주의사항

    • Thread-safe하지 않은 컬렉션: ArrayList, HashSet, HashMap 등 대부분의 Collection Framework 구현체는 Thread-safe하지 않습니다. 멀티스레드 환경에서 이러한 컬렉션을 사용하는 경우, 동기화 처리를 통해 데이터의 일관성을 유지해야 합니다. Collections.synchronizedList(), Collections.synchronizedSet(), Collections.synchronizedMap() 등의 메서드를 사용하여 Thread-safe한 컬렉션을 만들 수 있습니다. ConcurrentHashMap, CopyOnWriteArrayList와 같이 Thread-safe한 컬렉션 구현체도 제공됩니다.
    • Null 값 처리: 일부 컬렉션(예: HashMap)은 null 값을 키 또는 값으로 허용하지만, 다른 컬렉션(예: TreeMap)은 null 값을 허용하지 않습니다. 컬렉션을 사용할 때 null 값 처리 방식을 고려해야 합니다.
    • equals()와 hashCode() 메서드 재정의: HashSet, HashMap 등 해시 기반의 컬렉션을 사용하는 경우, 객체의 동등성을 올바르게 판단하기 위해 equals()hashCode() 메서드를 적절하게 재정의해야 합니다.
    • 컬렉션 크기 변경: 컬렉션의 크기를 변경하는 작업(예: add(), remove())은 성능에 영향을 미칠 수 있습니다. 특히, ArrayList와 같이 배열 기반의 리스트에서 중간에 요소를 삽입/삭제하는 경우, 다른 요소들을 이동시켜야 하므로 성능 저하가 발생할 수 있습니다. 대량의 데이터 삽입/삭제가 빈번하게 발생하는 경우에는 LinkedList와 같은 다른 자료구조를 고려하는 것이 좋습니다.
    • Iterator 사용 시 ConcurrentModificationException: 컬렉션을 순회하는 도중에 컬렉션의 구조를 변경하는 경우(예: remove()), ConcurrentModificationException이 발생할 수 있습니다. Iteratorremove() 메서드를 사용하거나, ListIterator를 사용하여 안전하게 요소를 삭제할 수 있습니다.
    • 제네릭 타입 사용: 제네릭 타입을 사용하여 컴파일 시점에 타입 오류를 검출하고, 코드의 가독성을 향상시키는 것이 좋습니다.
    • 적절한 자료구조 선택: 문제 해결에 적합한 자료구조를 선택하는 것이 중요합니다. List, Set, Map 각각의 특징을 이해하고, 데이터의 크기, 접근 빈도, 삽입/삭제 빈도 등을 고려하여 최적의 자료구조를 선택해야 합니다.

    이 글에서 다룬 내용들을 바탕으로 Java Collection Framework를 효과적으로 활용하여 효율적이고 안정적인 코드를 작성하시기 바랍니다. 궁금한 점이나 더 자세한 설명이 필요하시면 언제든지 댓글로 문의해주세요.

    728x90
    반응형