Java Collection Framework (JCF)

한솔·2025년 9월 30일

Java Collection Framework
컴퓨터 공학 자료 구조 과목 : List , Set , Map, Stack, Queue란?


public static void main(String[] args) {
        exampleList();
        exampleSet();
        exampleMap();
        exampleStackUsingDeque();
        exampleQueueUsignDeque();
        examplePriorityQueue();
    }

각 메서드를 만들어서 컬렉션에 대해 다뤄보도록 하겠다.

이번 목표는 개념을 이해하고 상황에 맞게 적절한 활용 예시이다.


list

에시: 쇼핑몰 장바구니 기능

private static void exampleList() {
        System.out.println("-------List start-------");

        List<String> list = new ArrayList<>();
        list.add("zebra");
        list.add("apple");
        list.add("monkey");
        list.add("banana");
        list.add("cherry"); //중복 가능, 순서 o
        System.out.println("////List :" + list);
        System.out.println("////List 1번째 :" + list.get(1));
        // 삽입
        list.add(1, "X");
        System.out.println("////List 1삽입 :" + list); // 결과: 삽입하고 뒤에 밀림
        // 삭제
        list.remove(2);
        System.out.println("////List 2삭제 :" + list);
        System.out.println();
    }

why?
사용자가 같은 상품을 두 번 담을 수 있다(중복 허용)
첫 번째 담은 상품처럼 인덱스로 접근이 가능해야 한다.

List<String> cart = new ArrayList<>();
cart.add("노트북");
cart.add("마우스");
cart.add("노트북"); // 중복 허용
System.out.println("장바구니: " + cart); // [노트북, 마우스, 노트북]
System.out.println("첫 번째 상품: " + cart.get(0)); // 노트북

Set

에시: 콘서트 예매 서비스에서 좌석 번호를 관리할 경우, 같은 좌석이 중복 예약되면 안됨

   private static void exampleSet() {
       System.out.println("=== Set (HashSet / LinkedHashSet / TreeSet) ===");

       // HashSet: 가장 흔함, 빠름, '순서 없음'
       Set<String> hashSet = new HashSet<>();
       hashSet.add("B");
       hashSet.add("A");
       hashSet.add("B"); // 중복은 무시
       System.out.println("HashSet (순서X) = " + hashSet);

       // LinkedHashSet: '입력 순서 유지'
       Set<String> linkedSet = new LinkedHashSet<>();
       linkedSet.add("B");
       linkedSet.add("A");
       linkedSet.add("B"); // 중복 무시
       System.out.println("LinkedHashSet (입력순서 유지) = " + linkedSet); // [B, A]

       // TreeSet: '자동 정렬' (기본 오름차순), null 불가
       Set<String> treeSet = new TreeSet<>();
       treeSet.add("B");
       treeSet.add("A");
       treeSet.add("C");
       System.out.println("TreeSet (정렬됨) = " + treeSet); // [A, B, C]

       // 언제 쓰나: 중복을 자동으로 제거해야 할 때(유니크한 값 모으기: 이메일, 태그 등)
       // 주의: HashSet은 출력 순서가 예측 불가 → 화면 표시용이면 LinkedHashSet 사용
       System.out.println();
   }
   

why?
중복 자동 제거, 순서 필요없다는 특징

Set<String> seats = new HashSet<>();
seats.add("A1");
seats.add("A2");
seats.add("A1"); // 중복 -> 무시됨

System.out.println("예약된 좌석: " + seats); // [A1, A2]

입력 순서를 유지해야 한다면 LinkedHashSet
좌석 번호를 자동 정렬하려면 TreeSet을 사용하면 된다.


    private static void exampleMap() {
        System.out.println("=== Map (HashMap / LinkedHashMap / TreeMap) ===");

        // HashMap: 빠름, 순서 없음
        Map<String, Integer> hashMap = new HashMap<>();
        hashMap.put("apple", 3);   // 키 "apple" → 값 3
        hashMap.put("banana", 1);
        hashMap.put("apple", 10);  // 같은 키에 put → 기존 값 3을 10으로 '덮어쓰기'
        System.out.println("HashMap size = " + hashMap.size());         // 2
        System.out.println("HashMap get('apple') = " + hashMap.get("apple")); // 10

        // LinkedHashMap: 입력 순서 유지(화면 표시용에 좋음)
        Map<String, Integer> linkedMap = new LinkedHashMap<>();
        linkedMap.put("apple", 3);
        linkedMap.put("banana", 1);
        linkedMap.put("cherry", 2);
        System.out.println("LinkedHashMap (입력순서 유지) = " + linkedMap); // {apple=3, banana=1, cherry=2}

        // TreeMap: 키 기준 정렬(사전순 등)
        Map<String, Integer> treeMap = new TreeMap<>();
        treeMap.put("banana", 1);
        treeMap.put("apple", 3);
        treeMap.put("cherry", 2);
        System.out.println("TreeMap (키 정렬) = " + treeMap); // {apple=3, banana=1, cherry=2}

        // Map 순회: entrySet()으로 키와 값을 함께 꺼내면 편함
        System.out.println("Iterate LinkedHashMap:");
        for (Map.Entry<String, Integer> e : linkedMap.entrySet()) {
            System.out.println("  " + e.getKey() + " -> " + e.getValue());
        }

        // 언제 쓰나: 기준(키)로 빠르게 찾아야 할 때(아이디→회원정보, 상품코드→상품)
        // 주의: 같은 키 put 시 덮어쓰기, HashMap은 순서X, TreeMap은 null 키 불가
        System.out.println();
    }
  • 학생 이름으로 점수를 관리할 경우, 이름(Key)->점수(value)

why?
키를 통해 빠르게 값 검색, 같은 이름이 들어오면 점수 덮어씀

단, 동명이인일 경우에는??
-> Map은Key가 유일해야 한다는 규칙이 있다.
그래서 HashMap, TreeMap, LinkedHashMep 전부 동일한 키가 들어오면 기존 값을 덮어쓴다.
그래서 동명이인일 경우에는 이름을 Key로 쓰지 않고 학번, ID같은 고유 식별자를 Key로 두면 된다.


Stack

에시: 텍스트 편집기에서 실행 취소 기능 구현 시

    // - 레거시 java.util.Stack 대신 Deque(ArrayDeque) 권장
   private static void exampleStackUsingDeque() {
       System.out.println("=== Stack (using Deque/ArrayDeque) ===");

       // Deque을 스택처럼 사용: push/pop/peek
       Deque<Integer> stack = new ArrayDeque<>();
       stack.push(1); // 맨 위에 1
       stack.push(2); // 맨 위에 2
       stack.push(3); // 맨 위에 3 (최상단)

       System.out.println("pop() -> " + stack.pop());  // 3 (가장 나중에 넣은 것)
       System.out.println("peek() -> " + stack.peek()); // 2 (위에 무엇이 있는지 보기만)

       // 언제 쓰나: 실행취소(UNDO), 역순 처리, 괄호검사 등 LIFO 로직
       // 왜 Deque? Stack은 동기화 등 오버헤드가 있고 레거시라 비권장. Deque가 더 가볍고 빠름.
       System.out.println();
   }
   

why?
마지막 동작부터 취소해야 함(LIFO)

Deque<String> undoStack = new ArrayDeque<>();
undoStack.push("글자 입력");
undoStack.push("굵게 적용");
undoStack.push("밑줄 적용");

System.out.println("실행 취소: " + undoStack.pop()); // 밑줄 적용 취소
System.out.println("다음 취소: " + undoStack.peek()); // 굵게 적용 (보기만)

Queue

예시: 은행 창구 대기 시스템, 먼저 온 손님이 먼저 처리되어야 함

    // - 보통 Deque(ArrayDeque)로 큐 연산(offer/poll/peek)
    private static void exampleQueueUsingDeque() {
        System.out.println("=== Queue (using Deque/ArrayDeque) ===");

        Deque<String> queue = new ArrayDeque<>();
        queue.offer("A"); // 뒤로 A
        queue.offer("B"); // 뒤로 B
        queue.offer("C"); // 뒤로 C

        System.out.println("poll() -> " + queue.poll()); // A (가장 먼저 들어온 것)
        System.out.println("peek() -> " + queue.peek()); // B (앞에 무엇이 있는지 보기만)

        // 언제 쓰나: 작업 대기열, BFS, 생산자-소비자(동시성 환경에서는 BlockingQueue 계열 사용)
        System.out.println();
    }

why?
FIFO구조가 딱 맞음

Deque<String> waitingQueue = new ArrayDeque<>();
waitingQueue.offer("손님1");
waitingQueue.offer("손님2");
waitingQueue.offer("손님3");

System.out.println("처리: " + waitingQueue.poll()); // 손님1
System.out.println("다음 대기: " + waitingQueue.peek()); // 손님2

PriorityQueue

예시: 고객센터에서 VIP고객 문의-> 일반 고객문의 순으로 처리해야 할 경우

       System.out.println("=== PriorityQueue (기본 오름차순) ===");

       // 작은 값이 먼저 나온다(기본 자연 순서)
       Queue<Integer> pq = new PriorityQueue<>();
       pq.offer(5);
       pq.offer(1);
       pq.offer(3);

       // poll()할 때마다 가장 '작은' 값부터 빠짐 → 1, 3, 5
       System.out.print("poll order = ");
       while (!pq.isEmpty()) {
           System.out.print(pq.poll() + " ");
       }
       System.out.println();

       // 내림차순 우선순위가 필요하면 Comparator 넘기기
       Queue<Integer> maxPq = new PriorityQueue<>(Comparator.reverseOrder());
       maxPq.offer(5);
       maxPq.offer(1);
       maxPq.offer(3);
       System.out.print("reverse poll order = ");
       while (!maxPq.isEmpty()) {
           System.out.print(maxPq.poll() + " "); // 5, 3, 1
       }
       System.out.println();

       // 언제 쓰나: “중요한 것 먼저 처리” (예: 가장 높은 점수, 가장 긴 대기시간 등)
       System.out.println();
   }
}

why?
우선순위 높은 요소 먼저 꺼내야 함

class Customer {
    String name;
    int priority; // 숫자가 작을수록 우선순위 높음
    Customer(String name, int priority) {
        this.name = name;
        this.priority = priority;
    }
    public String toString() { return name; }
}

PriorityQueue<Customer> pq = new PriorityQueue<>(Comparator.comparingInt(c -> c.priority));
pq.offer(new Customer("일반 고객", 5));
pq.offer(new Customer("VIP 고객", 1));
pq.offer(new Customer("일반 고객2", 3));

while (!pq.isEmpty()) {
    System.out.println("응대: " + pq.poll()); // VIP → 일반 고객2 → 일반 고객
}

0개의 댓글