CodeGym /행동 /JAVA 25 SELF /새로운 컬렉션: SequencedCollection, SequencedSet, SequencedMap

새로운 컬렉션: SequencedCollection, SequencedSet, SequencedMap

JAVA 25 SELF
레벨 34 , 레슨 4
사용 가능

1. 전통적인 컬렉션에서의 순서 문제

Java에는 언제나 요소의 순서를 보장하는 컬렉션(예: ArrayList, LinkedList, LinkedHashSet, LinkedHashMap)과 순서를 보장하지 않는 컬렉션(예: HashSet, HashMap)이 있었습니다. 하지만 이러한 컬렉션에는 공통된 단점이 하나 있었는데, 일부 컬렉션이 특정 순서로 요소를 저장하더라도 표준 인터페이스(List, Set, Map)는 첫/마지막 요소에 접근하거나 순서를 뒤집는 범용 메서드를 제공하지 않았다는 점입니다.

예를 들어 List<String>이 있다면 list.get(0)으로 첫 요소를 얻을 수 있지만, Set이나 Map에서는 이런 요령이 통하지 않습니다. 반복자를 사용하거나 불필요한 코드를 작성해야 하죠. 그리 편리하지도 않고 가독성에도 도움이 되지 않습니다.

마치 서랍장이 있는데 첫 번째나 마지막 서랍을 꺼내려면 매번 모든 서랍을 손으로 세어야 하는 것과 같습니다! "첫 번째"와 "마지막"이라는 전용 손잡이가 있으면 얼마나 좋을까요?

SequencedCollection 및 관련 인터페이스의 등장

Java 21에는 새로운 컬렉션 인터페이스가 추가되었습니다:

  • SequencedCollection<E>
  • SequencedSet<E>
  • SequencedMap<K, V>

이 인터페이스들은 표준 컬렉션을 확장하여 요소 순서를 다루는 일관된 방식을 도입합니다. 이제 순서가 중요한 어떤 컬렉션이든 구체적인 구현에 신경 쓰지 않고 범용 코드를 작성할 수 있습니다.

이게 무엇인가요?

  • SequencedCollection은 요소에 명확한 순서가 있으며, 첫 요소와 마지막 요소를 쉽게 얻고 순서를 뒤집어 볼 수 있는 컬렉션입니다.
  • SequencedSet은 고유한 요소를 갖는 Set에 동일한 개념을 적용한 것입니다.
  • SequencedMap은 키-값 쌍을 갖는 Map에 동일한 개념을 적용한 것입니다.

어떤 컬렉션이 이를 구현하나요?

Java 21에서 새로운 인터페이스를 구현하는 표준 컬렉션은 다음과 같습니다:

  • ArrayList, LinkedListSequencedCollection
  • LinkedHashSet, TreeSetSequencedSet
  • LinkedHashMap, TreeMapSequencedMap

즉, 이미 이러한 컬렉션을 사용 중이라면 자동으로 새로운 기능을 얻게 됩니다!

2. SequencedCollection, SequencedSet, SequencedMap의 주요 메서드

SequencedCollection의 메서드

E getFirst();      // 첫 번째 요소 가져오기
E getLast();       // 마지막 요소 가져오기
SequencedCollection<E> reversed(); // 역순 뷰 가져오기

SequencedSet의 메서드

SequencedCollection과 동일한 메서드에, Set이 제공하는 모든 기능이 더해집니다.

SequencedMap의 메서드

Map.Entry<K, V> firstEntry();    // 첫 엔트리(키-값)
Map.Entry<K, V> lastEntry();     // 마지막 엔트리(키-값)
SequencedMap<K, V> reversed();   // 역순 Map 뷰

3. 새로운 인터페이스 사용 예시

예제 1: 첫 번째와 마지막 요소 가져오기

import java.util.*;

public class SequencedDemo {
    public static void main(String[] args) {
        SequencedCollection<String> sc = new ArrayList<>();
        sc.add("Java");
        sc.add("Python");
        sc.add("Kotlin");

        // 첫 번째와 마지막 요소 가져오기
        String first = sc.getFirst(); // "Java"
        String last = sc.getLast();   // "Kotlin"

        System.out.println("첫 번째: " + first);
        System.out.println("마지막: " + last);
    }
}

출력:

첫 번째: Java
마지막: Kotlin

예제 2: 컬렉션 역순

SequencedCollection<String> sc = new LinkedList<>();
sc.add("A");
sc.add("B");
sc.add("C");

SequencedCollection<String> reversed = sc.reversed();
System.out.println(reversed); // [C, B, A]

주의: reversed()는 컬렉션을 역순으로 표현한 뷰를 반환합니다. 원본 컬렉션을 변경하면 reversed 뷰도 함께 변경됩니다!

예제 3: SequencedSet 사용

SequencedSet<Integer> set = new LinkedHashSet<>();
set.add(100);
set.add(200);
set.add(300);

System.out.println("첫 요소: " + set.getFirst()); // 100
System.out.println("마지막 요소: " + set.getLast()); // 300

SequencedSet<Integer> reversedSet = set.reversed();
System.out.println(reversedSet); // [300, 200, 100]

예제 4: SequencedMap 사용

SequencedMap<String, Integer> map = new LinkedHashMap<>();
map.put("apple", 5);
map.put("banana", 3);
map.put("cherry", 7);

Map.Entry<String, Integer> first = map.firstEntry();
Map.Entry<String, Integer> last = map.lastEntry();

System.out.println("첫 번째: " + first.getKey() + " = " + first.getValue()); // apple = 5
System.out.println("마지막: " + last.getKey() + " = " + last.getValue()); // cherry = 7

SequencedMap<String, Integer> reversedMap = map.reversed();
System.out.println(reversedMap); // {cherry=7, banana=3, apple=5}

4. 내 애플리케이션과 어떤 관련이 있나요?

학습용 애플리케이션에서 로그인한 사용자 목록을 저장하고, 첫 번째와 마지막 사용자를 빠르게 가져오고 싶다고 가정해 봅시다(예: "누가 가장 먼저 로그인했는지"와 "누가 마지막인지" 표시). 예전에는 다음과 같이 작성해야 했습니다:

List<String> users = new ArrayList<>();
// ... 사용자 추가
String first = users.get(0);
String last = users.get(users.size() - 1);

하지만 컬렉션이 리스트가 아니라 예를 들어 LinkedHashSet(요소가 고유하고 순서가 유지됨)이라면, 이런 요령은 통하지 않습니다:

Set<String> users = new LinkedHashSet<>();
// ... 사용자 추가
// 첫 사용자를 어떻게 가져오나? 반복자만 사용 가능:
String first = users.iterator().next();
// 마지막 사용자는? 전부 순회해야 함!

이제는 더 간단하고 범용적으로 처리할 수 있습니다:

SequencedSet<String> users = new LinkedHashSet<>();
// ... 사용자 추가
String first = users.getFirst();
String last = users.getLast();

이는 코드량을 줄일 뿐 아니라 더 읽기 쉽고 안전하게 만듭니다.

5. 새 인터페이스 개요

classDiagram
    Collection <|-- SequencedCollection
    List <|-- SequencedCollection
    Set <|-- SequencedSet
    Map <|-- SequencedMap
    SequencedCollection <|-- SequencedSet
    SequencedSet <|-- LinkedHashSet
    SequencedSet <|-- TreeSet
    SequencedCollection <|-- ArrayList
    SequencedCollection <|-- LinkedList
    SequencedMap <|-- LinkedHashMap
    SequencedMap <|-- TreeMap

6. 유용한 팁과 주의점

SequencedCollection의 실용적 장점

  • 순서를 다루기 위한 단일 인터페이스: 어디서는 get(0)을 쓰고, 어디서는 반복자가 필요하고, 어디서는 첫 요소를 아예 얻을 수 없는지 더 이상 외울 필요가 없습니다.
  • 큐와 스택 작업의 편의성: 첫(head) 요소와 마지막(tail) 요소를 쉽게 가져올 수 있습니다.
  • 안전성과 가독성: 컬렉션 오용에서 비롯되는 오류가 줄고, 코드가 자체적으로 의도를 드러냅니다.
  • 컬렉션을 빠르게 역순으로 보기: reversed()로 수작업 없이 손쉽게 역순 뷰를 얻을 수 있습니다.
  • 코드 유지보수와 확장의 용이성: 앞으로 예를 들어 ArrayListLinkedHashSet으로 바꾸고 싶어도, SequencedCollection을 사용하는 코드는 다시 작성할 필요가 없습니다.

구현상의 특징

  • 모든 컬렉션이 SequencedCollection을 구현하는 것은 아닙니다: 예를 들어 HashSetHashMap은 순서를 보장하지 않으므로 새로운 인터페이스를 구현하지 않습니다.
  • 메서드가 예외를 던질 수 있습니다: 컬렉션이 비어 있으면 getFirst()getLast() 호출 시 NoSuchElementException이 발생합니다. 컬렉션이 비어 있지 않은지 확인하세요!
  • reversed()는 복사가 아니라 "뷰"입니다: 원본 컬렉션의 변경 사항은 역순 뷰에도 반영되고 그 반대도 마찬가지입니다.
  • 호환성: 새로운 인터페이스는 Java 21부터 사용할 수 있습니다. 더 낮은 버전의 Java를 사용 중이라면 아직 이용할 수 없습니다(업데이트를 고려해 보세요!).
  • 제네릭(Generics): 모든 새로운 인터페이스는 제네릭을 완전히 지원하므로 어떤 데이터 타입과도 함께 사용할 수 있습니다.

7. SequencedCollection 사용 시 흔한 실수

오류 1: 순서를 보장하지 않는 컬렉션에서 순서를 기대함. HashSetSequencedSet으로 캐스팅하려고 하면 컴파일 오류가 발생합니다. HashSet에는 순서가 없으며 이 인터페이스를 구현하지 않습니다.

오류 2: 비어 있는 컬렉션을 무시함. 비어 있는 컬렉션에서 getFirst()getLast()를 호출하면 예외가 발생합니다. 이 메서드를 호출하기 전에 컬렉션이 비어 있지 않은지 확인하세요:

if (!sc.isEmpty()) {
    String first = sc.getFirst();
}

오류 3: reversed()에 대한 오해. reversed()는 복사가 아니라 뷰를 반환합니다. reversed 뷰를 변경하면 원본 컬렉션도 변경되고(그 반대도 마찬가지), 예상치 못한 결과를 초래할 수 있습니다.

오류 4: 오래된 Java 버전에서 새로운 인터페이스 사용. 프로젝트가 Java 21 미만 버전으로 컴파일된다면 컴파일러가 이 인터페이스들을 찾지 못합니다. 프로젝트 설정에서 JDK 버전을 확인하세요!

1
과제
JAVA 25 SELF, 레벨 34, 레슨 4
잠금
스마트 작업 디스패처: 작업 시작 전에 비어 있는지 확인하기 🤖
스마트 작업 디스패처: 작업 시작 전에 비어 있는지 확인하기 🤖
1
과제
JAVA 25 SELF, 레벨 34, 레슨 4
잠금
단계 알고리즘: 순방향 및 역방향 제어 🔄
단계 알고리즘: 순방향 및 역방향 제어 🔄
1
설문조사/퀴즈
현대 컬렉션, 레벨 34, 레슨 4
사용 불가능
현대 컬렉션
현대 컬렉션과 불변성
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION