우아한테크코스 레벨 1 자료를 참고하여 학습한 내용을 정리한 글입니다.
틀린 내용이나 그림이 있다면 댓글로 알려주시면 감사하겠습니다. 👐🏻
💭 들어가며
미션을 수행할 때 주로 List나 Set 같은 익숙한 컬렉션만 사용해 왔다. 하지만 LinkedList, Deque 등의 자료구조를 접하면서 컬렉션의 동작 방식과 적절한 활용 시점을 정확히 파악하지 못하고 있다는 생각이 들었다. 이러한 문제를 해결하기 위해, Java의 컬렉션 구현체들을 하나씩 분석하며 깊이 있게 공부할 필요가 있다고 느꼈다.
Java Collections Framework는 크게 인터페이스(Interface)와 구현체(Implementations)로 구성되어 있으며, 각 구현체는 특정한 상황에서 더 효율적인 성능을 제공한다. 이를 이해하고 적절하게 활용하는 것이 중요하다.
🔽 주요 인터페이스
- Iterable
- Collection
- List
- Queue
- Set
- Map
이 글에서 인터페이스의 주요 메서드는 대부분 포함했으며, 클래스에서는 인터페이스에서 상속받은 메서드를 제외하고 추가 메서드만 작성했다. 더 자세한 내용은 공식 문서를 참고하는 것이 좋을 것 같다.
✅ Iterable 인터페이스
Iterable<T> 인터페이스는 Iterator를 리턴할 수 있는 클래스를 의미한다. 컬렉션 인터페이스들의 최상위 인터페이스이다.
🔽 메서드
메서드 | 설명 |
default void forEach(Consumer<? super T> action) | 컬렉션의 모든 요소에 대해 action을 실행한다. |
Iterator<T> iterator() | 컬렉션을 하나씩 순회할 수 있도록 도와주는 Iterator 객체를 반환한다. |
default Spliterator<T> spliterator() | 컬렉션의 요소를 여러 개의 작은 부분으로 나누어 병렬 처리할 수 있도록 Spliterator 객체를 반환한다. |
✅ Collection 인터페이스
Collection 인터페이스는 Iterable을 상속하고 있으며, List, Set, Queue 등 공통된 메서드를 정의한다. 실제로 사용되는 자료구조들은 대부분 Collection 인터페이스를 직간접적으로 구현한다.
🔽 메서드
메서드 | 설명 |
boolean add(E e) |
요소를 컬렉션에 추가 |
boolean addAll(Collection<? extends E> c) | 다른 컬렉션의 모든 요소를 현재 컬렉션에 추가 |
void clear() | 컬렉션의 모든 요소 제거 |
boolean contains(Object o) | 특정 객체가 컬렉션에 포함되어 있는지 확인 |
boolean containsAll(Collection<?> c) | 지정된 컬렉션의 모든 객체가 현재 컬렉션에 포함되어 있는지 확인 |
boolean isEmpty() | 컬렉션이 비어있는지 확인 |
boolean remove(Object o) | 특정 객체를 컬렉션에서 제거 |
boolean removeAll(Collection<?> c) | 지정된 컬렉션에 포함된 모든 객체를 현재 컬렉션에서 제거 |
default boolean removeIf(Predicate<? super E> filter) |
주어진 조건에 맞는 객체들을 컬렉션에서 제거 |
boolean retainAll(Collection<?> c) | 현재 컬렉션에서 지정된 컬렉션에 포함된 요소만 유지하고 나머지는 제거 |
int size() | 컬렉션에 포함된 객체 개수 반환 |
default Stream<E> stream() |
컬렉션을 Stream으로 변환하여 스트림 API 활용 가능 |
Object[] toArray() <T> T[] toArray(T[] a) |
컬렉션의 요소를 배열로 변환 지정된 배열 타입으로 컬렉션을 변환 |
✅ List 인터페이스
List는 요소의 순서를 유지하며 중복을 허용하는 컬렉션 인터페이스이다.
🔽 메서드
메서드 | 설명 |
boolean add(E e) void add(int index, E element) |
리스트 끝에 요소 추가 지정된 위치에 요소 삽입 |
boolean addAll(Collection<? extends E> c) boolean addAll(int index, Collection<? extends E> c) |
지정된 컬렉션의 모든 요소를 리스트에 추가 지정된 위치에 다른 컬렉션의 모든 요소 삽입 |
void clear() | 리스트의 모든 요소 제거 |
boolean contains(Object o) | 특정 요소가 리스트에 포함되어 있는지 확인 |
boolean containsAll(Collection<?> c) | 지정된 컬렉션의 모든 요소가 리스트에 포함되어 있는지 확인 |
E get(int index) | 지정된 위치의 요소 반환 |
int indexOf(Object o) | 특정 요소의 첫 번째 인덱스 반환 (없을 시 -1 반환) |
boolean isEmpty() | 리스트가 비어있는지 확인 |
int lastIndexOf(Object o) | 특정 요소의 마지막 인덱스 반환 (없을 시 -1 반환) |
E remove(int index) boolean remove(Object o) |
지정된 위치의 요소 제거 및 반환 특정 요소 제거 |
boolean removeAll(Collection<?> c) | 지정된 컬렉션에 포함된 모든 요소 제거 |
default void replaceAll(UnaryOperator<E> operator) | 모든 요소를 지정된 연산을 통해 변경 |
boolean retainAll(Collection<?> c) | 지정된 컬렉션에 포함된 요소만 유지하고 나머지는 제거 |
E set(int index, E element) | 지정된 위치의 요소를 변경하고 이전 요소 반환 |
int size() | 리스트의 요소 개수 반환 |
default void sort(Comparator<? super E> c) | 지정된 Comparator를 사용해 List 정렬 |
List<E> subList(int fromIndex, int toIndex) | 지정된 범위의 요소를 포함하는 서브 리스트 반환 |
Object[] toArray() <T> T[] toArray(T[] a) |
리스트의 요소를 배열로 변환 지정된 배열 타입으로 리스트를 변환 |
▶ ArrayList 클래스
- 내부적으로 배열(Array)을 사용하여 요소를 관리한다.
- 요소의 순서를 유지하며 중복을 허용한다.
- 요소 접근 속도가 O(1)에 가까워, 조회가 빠르다.
- 중간에 요소를 삽입하거나 삭제할 경우 배열 복사가 필요하므로, 이러한 작업이 빈번하면 비효율적이다.
🔽 추가 메서드
메서드 | 설명 |
Object clone() | ArrayList의 복사본을 반환 |
void ensureCapacity(int minCapacity) | 내부 배열의 최소 용량을 지정 |
boolean removeIf(Predicate<? super E> filter) |
조건을 만족하는 모든 요소 제거 |
protected void removeRange(int fromIndex, int toIndex) | 지정된 범위의 요소를 제거 (서브 클래스에 사용 가능) |
void trimToSize() | 내부 배열 크기를 현재 요소 개수에 맞게 조정 |
List 인터페이스를 상속받은 후, ArrayList에서 추가된 메서드만 정리했다.
자세한 내용은 Java 공식 문서(ArrayList)를 참고하면 좋을 것 같다.
🔽 예시 코드
List<String> arrayList = new ArrayList<>();
arrayList.add("Miso");
arrayList.add("이소은");
System.out.println(arrayList.get(0)); // "Miso"
▶ LinkedList 클래스
LinkedList는 List와 Deque 인터페이스를 모두 상속받으므로, Deque 인터페이스 아래에서 설명하겠다.
▶ Vector 클래스
- 과거에 동기화(synchronized) 지원하기 위해 만든 ArrayList와 유사한 클래스이다.
- Thread-safe하지만, 동기화로 인해 단일 스레드에서 사용 시 오버헤드가 발생할 수 있다.
- 최근에는 잘 사용되지 않고, 컬렉션 동기화 필요 시 Collections.synchronizedList(new ArrayList<>()) 등을 사용한다.
🔽 예시 코드
List<String> vector = new Vector<>();
vector.add("Miso");
vector.add("이소은");
System.out.println(vector.get(0)); // Miso
▶ Stack 클래스
- LIFO(Last-In, First-Out) 구조를 가진다.
- 요소를 추가할 때는 push(), 제거할 때는 pop(), 맨 위 요소를 확인(제거 X)할 때는 peek()를 사용한다.
- Stack은 Vector를 상속받기 때문에 문제점이 많아, 최근에는 잘 사용되지 않는다.
- 마찬가지로 동기화(synchronized)가 적용되어 있어, 단일 스레드에서 사용 시 오버헤드가 발생할 수 있다.
- 최근에는 Deque(특히 ArrayDeque)를 활용하여 스택 기능을 대체하는 경우가 많다.
🔽 예시 코드
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
System.out.println(stack.peek()); // 2
System.out.println(stack.pop()); // 2
System.out.println(stack.peek()); // 1
✅ Queue 인터페이스
Queue는 FIFO(First-In, First-Out) 구조를 지원하기 위한 인터페이스이다. 데이터를 꺼내는 순서에 초점을 맞춘 자료구조이다.
🔽 메서드
메서드 | 설명 |
boolean add(E e) | 큐의 끝에 요소를 추가 저장 공간 부족 시 IllegalStateException 발생 |
E element() | 첫 번째 요소를 반환 비어 있을 시 NoSuchElementException 발생 |
boolean offer(E e) | 큐의 끝에 요소를 추가 |
E peek() | 첫 번째 요소를 반환 (제거 X) 비어 있을 시 null 반환 |
E poll() | 첫 번째 요소 제거 후 반환 비어 있을 시 null 반환 |
E remove() | 첫 번째 요소 제거 후 반환 비어 있을 시 NoSuchElementException 발생 |
▶ PriorityQueue 클래스
- 우선순위 큐를 구현한 클래스이다.
- 내부적으로 힙(Heap) 구조를 사용한다.
- 요소가 들어올 때마다 내부적으로 우선순위가 정렬되며, 가장 높은 우선순위를 가진 요소가 먼저 나가게 된다.
- 기본적으로 오름차순 기준이므로 가장 작은 값이 먼저 나간다. (최소 힙(Min-Heap) 기반으로 구현되어 있어서)
- 정렬 기준을 바꾸고 싶다면 Comparator를 사용해 우선순위를 재정의할 수 있다.
🔽 추가 메서드
메서드 | 설명 |
void clear() | 모든 요소 제거 |
Comparator<? super E> comparator() | PriorityQueue의 정렬 방식을 반환 (기본 정렬이면 null 반환) |
boolean contains(Object o) | 특정 요소가 PriorityQueue에 존재하는지 확인 |
boolean remove(Object o) | 특정 요소 제거 (일반 Queue보다 느림) |
int size() | PriorityQueue의 요소 개수 반환 |
Object[] toArray() T[] toArray(T[] a) |
PriorityQueue의 요소를 배열로 변환 지정된 타입의 배열로 변환 |
Queue 인터페이스를 상속받은 후, PriorityQueue에서 추가된 메서드만 정리했다.
자세한 내용은 Java 공식 문서(PriorityQueue)를 참고하면 좋을 것 같다.
🔽 예시 코드
Queue<Integer> priorityQueue = new PriorityQueue<>();
priorityQueue.offer(3);
priorityQueue.offer(1);
priorityQueue.offer(2);
System.out.println(priorityQueue.poll()); // 1
System.out.println(priorityQueue.poll()); // 2
System.out.println(priorityQueue.poll()); // 3
▶ Deque 인터페이스
Deque(Double-Ended Queue)는 양쪽(앞뒤)에서 요소를 추가하고 제거할 수 있는 Queue 인터페이스이다.
- Deque는 양방향 삽입/삭제가 가능하다.
- Queue처럼 FIFO로 사용 가능하다.
- Stack처럼 LIFO로도 사용 가능(push(), pop())하다.
🔽 메서드
메서드 | 설명 |
boolean add(E e) | 요소를 추가 저장 공간 부족 시 IllegalStateException 발생 |
void addFirst(E e) | 첫 번째에 요소 추가 저장 공간 부족 시 IllegalStateException 발생 |
void addLast(E e) | 마지막에 요소 추가 저장 공간 부족 시 IllegalStateException 발생 |
boolean contains(Object o) | 특정 요소가 포함되어 있는지 확인 |
Iterator<E> descendingIterator() | 역순으로 순회할 수 있는 Iterator 반환 |
E element() | 첫 번째 요소 조회 (제거 X) 비어 있을 시 NoSuchElementException 발생 |
E getFirst() | 첫 번째 요소 조회 (제거 X) 비어 있을 시 NoSuchElementException 발생 |
E getLast() | 마지막 요소 조회 (제거 X) 비어 있을 시 NoSuchElementException 발생 |
boolean offer(E e) | 요소 추가 실패 시 false 반환 |
boolean offerFirst(E e) | 첫 번째에 요소 추가 실패 시 false 반환 |
boolean offerLast(E e) | 마지막에 요소 추가 실패 시 false 반환 |
E peek() | 첫 번째 요소 조회 (제거 X) 비어 있을 시 null 반환 |
E peekFirst() | 첫 번째 요소 조회 (제거 X) 비어 있을 시 null 반환 |
E peekLast() | 마지막 요소 조회 (제거 X) 비어 있을 시 null 반환 |
E poll() | 첫 번째 요소 제거 후 반환 비어 있을 시 null 반환 |
E pollFirst() | 첫 번째 요소 제거 후 반환 비어 있을 시 null 반환 |
E pollLast() | 마지막 요소 제거 후 반환 비어 있을 시 null 반환 |
E pop() | 첫 번째 요소 제거 후 반환 |
void push(E e) | 첫 번째에 요소 추가 |
E remove() |
첫 번째 요소 제거 후 반환 비어 있을 시 NoSuchElementException 발생 |
boolean remove(Object o) | 특정 요소 제거 |
E removeFirst() | 첫 번째 요소 제거 후 반환 비어 있을 시 NoSuchElementException 발생 |
boolean removeFirstOccurrence(Object o) | 처음 등장하는 특정 요소 제거 |
E removeLast() | 마지막 요소 제거 후 반환 비어 있을 시 NoSuchElementException 발생 |
boolean removeLastOccurrence(Object o) | 마지막으로 등장하는 특정 요소 제거 |
int size() | 요소 개수 반환 |
▶ ArrayDeque 클래스
- 배열을 사용하여 양방향 삽입/삭제가 가능한 Deque 구현체이다.
- 일반적인 Queue 대신 사용되며, Stack의 기능도 대체 가능하다.
- LinkedList보다 메모리 사용량이 적고, 성능이 뛰어나다. (연결 리스트의 노드 오버헤드가 없기 때문이다.)
- 중간 삽입/삭제가 필요 없는 큐 또는 스택 구조로서 매우 빠르다(O(1)).
🔽 추가 메서드
메서드 | 설명 |
void clear() | 모든 요소 제거 |
ArrayDeque<E> clone() | ArrayDeque의 복사본을 반환 |
boolean isEmpty() | ArrayDeque가 비어있는지 확인 |
Object[] toArray() <T> T[] toArray(T[] a) |
요소를 포함하는 배열 반환 지정된 타입의 배열로 변환 |
Deque 인터페이스를 상속받은 후, ArrayDeque에서 추가된 메서드만 정리했다.
자세한 내용은 Java 공식 문서(ArrayDeque)를 참고하면 좋을 것 같다.
🔽 예시 코드
Deque<String> arrayDeque = new ArrayDeque<>();
arrayDeque.offerLast("Miso");
arrayDeque.offerFirst("이소은");
System.out.println(arrayDeque.pollFirst()); // "이소은"
System.out.println(arrayDeque.pollLast()); // "Miso"
🔽 ArrayDeque VS LinkedList
LinkedList | ArrayDeque | |
내부 구조 | 이중 연결 리스트 (노드 기반) | 동적 크기 배열 (배열 기반) |
삽입/삭제 속도 | O(1) (노드 연결 변경) | O(1) (배열 확장 발생 시 O(n)) |
조회 속도 | O(n) | O(n) |
메모리 사용 | 높음 (각 노드가 prev, next 포인터 포함) | 낮음 (배열 기반, 노드 오버헤드 없음) |
배열 확장 발생 가능성 | 없음 | 있음 (배열이 가득 차면 확장 필요) |
- ArrayDeque
- 메모리를 절약하고 싶을 때
- Stack을 대체할 때
- 빠른 삽입/삭제가 필요하지만, 중간 조회가 필요하지 않을 때
- LinkedList
- 연결 리스트의 특성을 활용해야 할 때
- List의 기능을 모두 사용해야 할 때
- 배열 확장 비용을 피하고 싶을 때
▶ LinkedList 클래스
- 내부적으로 이중 연결 리스트(Doubly Linked List)로 구성된다.
- ArrayList와 달리 Deque 인터페이스를 구현하므로 양쪽에서 요소 추가/삭제가 가능하다.
- 중간에 요소를 삽입하거나 삭제할 경우 노드 연결만 변경하면 되므로 빠르다.
- 요소 접근은 첫 노드부터 순회해야 하므로 O(n)의 시간이 걸려, 조회가 빈번한 경우 비효율적이다.
🔽 추가 메서드
메서드 | 설명 |
Object clone() | LinkedList의 복사본을 반환 |
protected void removeRange(int fromIndex, int toIndex) | 지정된 범위의 요소를 제거 (서브 클래스에 사용 가능) |
List 인터페이스와 Deque 인터페이스를 상속받은 후, LinkedList에서 추가된 메서드만 정리했다.
자세한 내용은 Java 공식 문서(LinkedList)를 참고하면 좋을 것 같다.
🔽 예시 코드
List<String> linkedList = new LinkedList<>();
linkedList.add("이소은");
linkedList.add("소은");
linkedList.addFirst("Miso");
linkedList.removeLast();
System.out.println(linkedList); // [Miso, 이소은]
System.out.println(linkedList.getFirst()); // "Miso"
✅ Set 인터페이스
Set은 요소의 중복을 허용하지 않는 컬렉션 인터페이스이다. 기본적으로 순서를 보장하지 않지만, 특정 구현체에서는 순서를 유지하거나 정렬할 수 있다.
🔽 메서드
메서드 | 설명 |
boolean add(E e) | 요소 추가 이미 존재하는 경우 false 반환 |
boolean addAll(Collection<? extends E> c) | 지정된 Collection의 모든 요소 추가 |
void clear() | 모든 요소 제거 |
boolean contains(Object o) | 특정 요소가 포함되어 있는지 확인 |
boolean containsAll(Collection<?> c) | 지정된 Collection의 모든 요소가 포함되어 있는지 확인 |
boolean isEmpty() | Set이 비어 있는지 확인 |
boolean remove(Object o) | 특정 요소 제거 |
boolean removeAll(Collection<?> c) | 지정된 Collection의 모든 요소 제거 |
boolean retainAll(Collection<?> c) | 지정된 Collection에 포함된 요소만 유지하고 나머지는 제거 |
int size() | Set의 요소 개수 반환 |
Object[] toArray() <T> T[] toArray(T[] a) |
Set의 요소를 배열로 변환 지정된 배열 타입으로 변환 |
▶ HashSet 클래스
- 순서를 보장하지 않으며, 중복을 허용하지 않는 Set 구현체이다.
- 내부적으로 HashMap을 사용하여 요소를 저장한다. (Key로 저장, Value는 dummy)
- 평균적으로 O(1)에 가까운 삽입/삭제/검색 성능을 가진다.
🔽 추가 메서드
메서드 | 설명 |
Object clone() | HashSet의 복사본을 반환 |
Set 인터페이스를 상속받은 후, HashSet에서 추가된 메서드만 정리했다.
자세한 내용은 Java 공식 문서(HashSet)를 참고하면 좋을 것 같다.
🔽 예시 코드
Set<String> hashSet = new HashSet<>();
hashSet.add("Miso");
hashSet.add("이소은");
hashSet.add("Miso"); // 이미 존재 -> 추가되지 않음
System.out.println(hashSet.size()); // 2
System.out.println(hashSet.contains("이소은")); // true
▶ LinkedHashSet 클래스
- HashSet과 달리, 삽입 순서를 유지하는 Set 구현체이다.
- 내부적으로는 LinkedList와 HashMap이 혼합된 구조로 되어 있어, 삽입 순서를 기록하는 연결 리스트가 존재한다.
- 성능은 HashSet보다는 약간 떨어지지만, 순서를 유지해야 하는 경우 유용하다.
🔽 추가 메서드
Set 인터페이스를 상속받고 HashSet을 확장한 LinkedHashSet은 추가된 메서드가 spliterator() 하나뿐이다.
자세한 내용은 Java 공식 문서(LinkedHashSet)를 참고하면 좋을 것 같다.
🔽 예시 코드
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Miso");
linkedHashSet.add("이소은");
linkedHashSet.add("소은");
// "Miso" -> "이소은" -> "소은"
for(String item : linkedHashSet) {
System.out.println(item);
}
HashSet VS LinkedHashSet
- HashSet은 내부적으로 HashMap을 사용하여 원소를 저장한다. 이때 해시 충돌이 일어나면 같은 버킷 내에서 체이닝(연결 리스트)(또는 트리화)로 원소들을 연결한다. 순서를 보장하지 않는다.
- 반면, LinkedHashSet은 내부적으로 LinkedHashMap을 사용하여 원소를 저장한다. 즉 충돌 처리는 여전히 체이닝(또는 트리화) 방식으로 처리하고, 그와 별개로 모든 원소를 연결하는 이중 연결 리스트를 유지하여 삽입 순서(또는 액세스 순서)를 기억한다. 이로 인해 HashSet보다 약간의 오버헤드가 있지만, 순서를 보장해야 할 때 유용하다.
▶ SortedSet 인터페이스
- Set 인터페이스를 확장한 정렬된 Set 인터페이스이다.
- 기본적으로 오름차순 정렬을 유지하며, Comparator를 사용하면 정렬 기준을 변경할 수 있다.
- 중복 요소를 허용하지 않으며, 정렬된 순서를 유지한다.
🔽 메서드
메서드 | 설명 |
Comparator<? super E> comparator() | 현재 정렬에 사용된 Comparator 반환 null이면 기본 정렬 |
E first() | 가장 첫 번째(작은) 요소 반환 |
SortedSet<E> headSet(E toElement) | 특정 요소보다 작은 값들로 구성된 SortedSet 반환 |
E last() | 가장 마지막(큰) 요소 반환 |
SortedSet<E> subSet(E fromElement, E toElement) | 지정된 범위의 요소를 포함하는 SortedSet 반환 |
SortedSet<E> tailSet(E fromElement) | 특정 요소 이상인 값들로 구성된 SortedSet 반환 |
▶ NavigableSet 인터페이스
- SortedSet은 단순히 정렬된 상태를 유지하는 인터페이스인 반면, NavigableSet은 더 다양한 탐색 및 정렬 기능을 제공한다.
- 특정 값보다 크거나 작은 값 찾기
- 가까운 요소 탐색
- 범위 내 요소 필터링
- 내림차순 정렬
🔽 추가 메서드
메서드 | 설명 |
E ceiling(E e) | 주어진 값 이상인 가장 작은 요소 반환 없을 시 null 반환 |
Iterator<E> descendingIterator() | 내림차순으로 순회하는 Iterator 반환 |
NavigableSet<E> descendingSet() | 내림차순 정렬된 NavigableSet 반환 |
E floor(E e) | 주어진 값 이하인 가장 큰 요소 반환 없을 시 null 반환 |
NavigableSet<E> headSet(E toElement, boolean inclusive) | 특정 값 이전의 요소들을 포함하는 NavigableSet 반환 inclusive가 true이면 해당 값 포함 |
E higher(E e) | 주어진 값보다 큰 요소 중 가장 작은 값 반환 없을 시 null 반환 |
E lower(E e) | 주어진 값보다 작은 요소 중 가장 큰 값 반환 없을 시 null 반환 |
E pollFirst() | 첫 번째 요소 제거 후 반환 없을 시 null 반환 |
E pollLast() | 마지막 요소 제거 후 반환 없을 시 null 반환 |
NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) | 지정된 범위 내 요소를 포함하는 NavigableSet 반환 |
NavigableSet<E> tailSet(E fromElement, boolean inclusive) | 특정 값 이후의 요소들을 포함하는 NavigableSet 반환 inclusive가 true이면 해당 값 포함 |
SortedSet 인터페이스를 상속받은 후, NavigableSet에서 추가된 메서드만 정리했다.
자세한 내용은 Java 공식 문서(NavigableSet)를 참고하면 좋을 것 같다.
▶ TreeSet 클래스
- SortedSet과 NavigableSet 인터페이스를 구현한 정렬된 Set 구현체이다.
- 내부적으로 이진 검색 트리(Red-Black Tree)를 사용하여 요소를 정렬된 상태로 저장한다.
- 기본적으로 오름차순 정렬이지만, Comparator를 사용해 정렬 기준을 바꿀 수 있다.
- 삽입/삭제 시에는 평균적으로 O(log n)의 시간 복잡도를 가진다.
🔽 추가 메서드
메서드 | 설명 |
Object clone() | TreeSet의 복사본을 반환 |
SortedSet과 NavigableSet을 상속받은 후, TreeSet에서 추가된 메서드만 정리했다.
자세한 내용은 Java 공식 문서(TreeSet)를 참고하면 좋을 것 같다.
🔽 예시 코드
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(3);
treeSet.add(1);
treeSet.add(2);
// 1 -> 2 -> 3 (오름차순 정렬)
for(Integer num : treeSet) {
System.out.println(num);
}
✅ Map 인터페이스
Map은 키(key)와 값(value)의 쌍으로 구성된 데이터를 처리하는 인터페이스이다.
- 값(value)는 중복이 허용되지만, 키(key)는 해당 Map에서 고유해야 한다.
- 저장 순서가 유지되지 않는다.
- Collection의 하위 인터페이스는 아니지만, Java Collections Framework로 함께 분류된다.
🔽 메서드
메서드 | 설명 |
void clear() | 모든 키-값 쌍을 제거 |
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) | 주어진 키의 값을 새로운 값으로 계산하여 갱신 |
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) | 특정 키가 없을 시 새로운 값을 계산하여 추가 |
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) | 특정 키가 존재하면 새로운 값을 계산하여 갱신 |
boolean containsKey(Object key) | 특정 키가 존재하는지 확인 |
boolean containsValue(Object value) | 특정 값이 존재하는지 확인 |
Set<Map.Entry<K, V>> entrySet() | 모든 키-값 쌍을 Set<Map.Entry<K,V>> 형태로 반환 |
default forEach(BiConsumer<? super K, ? super V> action) | 모든 요소에 대해 지정된 동작 수행 (key-value 반복 처리) |
V get(Object key) | 주어진 키에 해당하는 값 반환 없을 시 null 반환 |
default V getOrDefault(Object key, V defaultValue) | 주어진 키에 대한 값이 없을 시 기본값 반환 |
boolean isEmpty() | Map이 비어있는지 확인 |
Set<K> keySet() | 모든 키를 Set 형태로 반환 |
default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) | 키가 존재하면 병합 수행, 존재하지 않을 시 새 값 추가 |
V put(K key, V value) | 새로운 키-값 추가 (키가 존재하면 값 변경) |
void putAll(Map<? extends K, ? extends V> m) | 다른 Map의 모든 키-값을 추가 |
default V putIfAbsent(K key, V value) | 키가 존재하지 않을 경우만 값 추가 |
V remove(Object key) | 특정 키에 해당하는 값을 제거 |
default boolean remove(Object key, Object value) | 특정 키가 주어진 값과 매칭될 경우 제거 |
default V replace(K key, V value) | 특정 키의 값을 새로운 값으로 교체 |
default boolean replace(K key, V oldValue, V newValue) | 특정 키의 기존 값이 oldValue와 일치하면 newValue로 교체 |
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) | 모든 값에 대해 주어진 연산 수행 후 변경 |
int size() | Map에 저장된 요소 개수 반환 |
Collection<V> values() | 모든 값을 Collection 형태로 반환 |
▶ Hashtable 클래스
- 초기 자바부터 제공되던 Map 구현체이다.
- HashMap과 유사하지만, 동기화(synchronized) 처리가 되어 있다.
- 동기화로 인해 단일 스레드에서 사용 시 오버헤드가 발생할 수 있어 거의 사용되지 않는다.
- 멀티 스레드 환경에서는 Hashtable보다는 ConcurrentHashMap 등을 사용하는 것이 더 좋다.
🔽 예시 코드
Map<String, Integer> hashtable = new Hashtable<>();
hashtable.put("Miso", 1);
hashtable.put("미소", 2);
System.out.println(hashtable.get("Miso")); // 1
▶ HashMap 클래스
- 가장 많이 사용되는 Map 구현체이다.
- 내부적으로 배열(Array) + 연결 리스트(Chaining) 또는 트리(Tree, Java 8 이상) 구조를 사용하여 데이터를 저장한다.
- 키(Key)는 중복을 허용하지 않으며, 값(Value)은 중복이 가능하다.
- null 키는 1개까지 허용하며, null 값은 여러 개 가질 수 있다.
- 평균적으로 O(1)의 시간 복잡도로 삽입/삭제/검색이 가능하다. (해시 충돌이 적절히 관리된다는 전제)
🔽 메서드
메서드 | 설명 |
Object clone() | HashMap의 복사본을 반환 |
Map 인터페이스를 상속받은 후, HashMap에서 추가된 메서드만 정리했다.
자세한 내용은 Java 공식 문서(HashMap)를 참고하면 좋을 것 같다.
🔽 예시 코드
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("Miso", 1);
hashMap.put("이소은", 2);
hashMap.put("Miso", 3); // 기존 값 업데이트
System.out.println(hashMap.get("Miso")); // 3
System.out.println(hashMap.containsKey("소은")); // false
▶ LinkedHashMap 클래스
- HashMap + 양방향 연결 리스트(Doubly Linked List)를 사용하는 Map 구현체이다.
- 요소의 삽입 순서 또는 접근 순서를 유지할 수 있다.
- 삽입 순서를 유지: new LinkedHashMap<>(initialCapacity, loadFactor, false)
- 접근 순서를 유지: new LinkedHashMap<>(initialCapacity, loadFactor, true)
🔽 메서드
메서드 | 설명 |
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) | 가장 오래된 요소를 제거할지 여부를 결정 LRU 캐시 구현에 활용 가능 |
Map 인터페이스를 상속받은 후, LinkedHashMap에서 추가된 메서드만 정리했다.
자세한 내용은 Java 공식 문서(LinkedHashMap)를 참고하면 좋을 것 같다.
🔽 예시 코드
Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("Miso", 1);
linkedHashMap.put("이소은", 2);
linkedHashMap.put("소은", 3);
// "Miso" -> "이소은" -> "소은"
for (String key : linkedHashMap.keySet()) {
System.out.println(key);
}
▶ SortedMap 인터페이스
- Map 인터페이스를 확장한 정렬된 Map 인터페이스이다.
- 기본적으로 오름차순 정렬을 유지하며, Comparator를 사용하면 정렬 기준을 변경할 수 있다.
- 키를 기준으로 정렬된 상태로 요소를 저장한다.
🔽 메서드
메서드 | 설명 |
Comparator<? super K> comparator() | 사용 중인 정렬 기준(Comparator)을 반환 null이면 기본 정렬 |
Set<Map.Entry<K,V>> entrySet() | 모든 키-값 쌍을 Set 형태로 반환 |
K firstKey() | 가장 첫 번째 키를 반환 |
SortedMap<K,V> headMap(K toKey) | 지정된 키보다 작은 요소들로 구성된 SortedMap 반환 |
Set<K> keySet() | 모든 키를 Set 형태로 반환 |
K lastKey() | 가장 마지막 키를 반환 |
SortedMap<K,V> subMap(K fromKey, K toKey) | 범위(fromKey~toKey) 내의 요소들로 구성된 SortedMap 반환 |
SortedMap<K,V> tailMap(K fromKey) | 지정된 키보다 크거나 같은 요소들로 구성된 SortedMap 반환 |
Collection<V> values() | 모든 값(Value)을 Collection 형태로 반환 |
▶ NavigableMap 인터페이스
SortedMap은 단순히 정렬된 상태를 유지하는 인터페이스인 반면, NavigableMap은 키를 기준으로 범위를 검색하거나 가까운 키를 찾는 기능을 제공한다.
🔽 추가 메서드
메서드 | 설명 |
Map.Entry<K, V> ceilingEntry(K key) | 주어진 키 이상에서 가장 가까운 키-값 쌍 반환 |
K ceilingKey(K key) | 주어진 키 이상에서 가장 작은 키 반환 |
NavigableSet<K> descendingKeySet() | 내림차순 정렬된 키들의 NavigableSet 반환 |
NavigableMap<K, V> descendingMap() | 내림차순 정렬된 NavigableMap 반환 |
Map.Entry<K, V> firstEntry() | 가장 첫 번째 키-값 쌍 반환 |
Map.Entry<K, V> floorEntry(K key) | 주어진 키 이하에서 가장 가까운 키-값 쌍 반환 |
K floorKey(K key) | 주어진 키 이하에서 가장 큰 키 반환 |
Map.Entry<K, V> higherEntry(K key) | 주어진 키보다 큰 가장 작은 키-값 쌍 반환 |
NavigableMap<K, V> headMap(K toKey, boolean inclusive) | 지정된 키보다 작은 요소를 포함하는 NavigableMap 반환 inclusive 설정 가능 |
K higherKey(K key) | 주어진 키보다 큰 가장 작은 키 반환 |
Map.Entry<K, V> lastEntry() | 가장 마지막 키-값 쌍 반환 |
Map.Entry<K, V> lowerEntry(K key) | 주어진 키보다 작은 값 중 가장 큰 키-값 쌍 반환 |
K lowerKey(K key) | 주어진 키보다 작은 가장 큰 키 반환 |
NavigableSet<K> navigableKeySet() | 정렬된 키들의 NavigableSet 반환 |
Map.Entry<K, V> pollFirstEntry() | 가장 첫 번째 키-값 쌍 반환 후 제거 |
Map.Entry<K, V> pollLastEntry() | 가장 마지막 키-값 쌍 반환 후 제거 |
NavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) | 범위 내 요소를 포함하는 NavigableMap 반환 fromInclusive, toInclusive 설정 가능 |
NavigableMap<K, V> tailMap(K fromKey, boolean inclusive) | 지정된 키보다 큰 요소를 포함하는 NavigableMap 반환 inclusive 설정 가능 |
SortedMap 인터페이스를 상속받은 후, NavigableMap에서 추가된 메서드만 정리했다.
자세한 내용은 Java 공식 문서(NavigableMap)를 참고하면 좋을 것 같다.
▶ TreeMap 클래스
- 내부적으로 이진 검색 트리(Red-Black Tree)를 사용하여 정렬된 순서로 키-값을 저장한다.
- 기본적으로 키를 오름차순으로 정렬하지만, Comparator를 주입하면 정렬 기준을 재정의할 수 있다.
- 삽입/삭제/검색 모두 O(log n)의 시간 복잡도를 가진다.
🔽 메서드
메서드 | 설명 |
Object clone() | TreeMap의 복사본을 반환 |
boolean replace(K key, V oldValue, V newValue) | 특정 키가 주어진 값과 매칭되면 새 값으로 변경 후 true 반환, 아니면 false 반환 |
V replace(K key, V value) | 해당 키가 존재하면 값을 변경 후 기존 값 반환, 존재하지 않을 시 변경하지 않고 null 반환 |
void replaceAll(BiFunction<? super K,? super V,? extends V> function) | 모든 값을 주어진 함수로 변환하여 갱신 |
void forEach(BiConsumer<? super K, ? super V> action) | 모든 엔트리에 대해 주어진 동작 수행 |
Map과 SortedMap과 NavigableMap을 상속받은 후, TreeMap에서 추가된 메서드만 정리했다.
자세한 내용은 Java 공식 문서(TreeMap)를 참고하면 좋을 것 같다.
🔽 예시 코드
Map<Integer, String> treeMap = new TreeMap<>();
treeMap.put(2, "이소은");
treeMap.put(1, "Miso");
treeMap.put(3, "소은");
// 1: Miso, 2: 이소은, 3: 소은
for (Map.Entry<Integer, String> entry : treeMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
📍 참고 자료
- Java 공식 문서 - Iterable
- Java 공식 문서 - Collection
- Java 공식 문서 - List
- Java 공식 문서 - ArrayList
- Java 공식 문서 - LinkedList
- Java 공식 문서 - Queue
- Java 공식 문서 - PriorityQueue
- Java 공식 문서 - Deque
- Java 공식 문서 - ArrayDeque
- Java 공식 문서 - Set
- Java 공식 문서 - HashSet
- Java 공식 문서 - LinkedHashSet
- Java 공식 문서 - SortedSet
- Java 공식 문서 - NavigableSet
- Java 공식 문서 - TreeSet
- Java 공식 문서 - Map
- Java 공식 문서 - HashMap
- Java 공식 문서 - LinkedHashMap
- Java 공식 문서 - SortedMap
- Java 공식 문서 - NavigableMap
- Java 공식 문서 - TreeMap
- 🧱 Java Collections Framework 종류 💯 총정리
'Programming > Java' 카테고리의 다른 글
[Java] 인터페이스, 추상 클래스, 일반 상속 (0) | 2025.03.22 |
---|---|
[Java] 제네릭(Generic) (0) | 2025.03.17 |
[Java] 람다 표현식, 함수형 인터페이스, Stream API (0) | 2025.02.23 |
[Java] 테스트 코드, JUnit & AssertJ (0) | 2025.02.21 |
[Java] Enum (5) | 2024.11.05 |