Java Collection
Java Collection Framework는 데이터 구조 및 알고리즘을 제공하여 데이터를 효율적으로 관리하고 조작할 수 있게 합니다. 이 프레임워크에는 다양한 인터페이스와 클래스를 포함하며, 주로 List, Set, Map 인터페이스를 중심으로 구성됩니다. 각 인터페이스는 고유한 특성과 기능을 제공하며, 이들을 통해 데이터를 저장하고 조작할 수 있습니다. 또한, Java Collection Framework는 동기화된(Synchronized) 및 동시성(Concurrent)을 지원하여 멀티스레드 환경에서도 안전하게 사용할 수 있도록 합니다.
1. List 인터페이스
List 인터페이스는 순서가 있는 요소의 컬렉션을 정의합니다. 중복 요소를 허용하며, 인덱스를 통해 요소에 접근할 수 있습니다.
주요 구현 클래스
- ArrayList: 동적 배열을 사용하여 요소를 저장합니다. 배열 기반이므로 요소 접근이 빠르지만, 중간에 요소를 삽입하거나 삭제하는 작업은 느릴 수 있습니다.
- LinkedList: 이중 연결 리스트를 사용하여 요소를 저장합니다. 삽입 및 삭제가 빠르지만, 요소 접근 속도는 느릴 수 있습니다.
- Vector: ArrayList와 유사하지만, 기본적으로 동기화됩니다. 멀티스레드 환경에서 안전하게 사용할 수 있습니다.
- Stack: 후입선출(LIFO) 스택을 구현한 클래스입니다. Vector를 상속받아 동기화됩니다.
2. Set 인터페이스
Set 인터페이스는 중복 요소를 허용하지 않는 컬렉션을 정의합니다. 순서를 보장하지 않으며, 각 요소는 유일해야 합니다.
주요 구현 클래스
- HashSet: 해시 테이블을 사용하여 요소를 저장합니다. 요소의 순서를 보장하지 않으며, 빠른 검색을 제공합니다.
- LinkedHashSet: HashSet과 유사하지만, 삽입 순서를 유지합니다.
- TreeSet: 이진 검색 트리를 사용하여 요소를 저장합니다. 요소를 정렬된 상태로 유지하며, 검색, 삽입, 삭제 작업이 균등하게 빠릅니다.
3. Map 인터페이스
Map 인터페이스는 키-값 쌍을 저장하는 컬렉션을 정의합니다. 각 키는 유일해야 하며, 값은 중복을 허용합니다.
주요 구현 클래스
- HashMap: 해시 테이블을 사용하여 키-값 쌍을 저장합니다. 순서를 보장하지 않으며, 빠른 검색을 제공합니다.
- LinkedHashMap: HashMap과 유사하지만, 삽입 순서를 유지합니다.
- TreeMap: 이진 검색 트리를 사용하여 키-값 쌍을 저장합니다. 키를 정렬된 상태로 유지하며, 검색, 삽입, 삭제 작업이 균등하게 빠릅니다.
- Hashtable: HashMap과 유사하지만, 동기화됩니다. 멀티스레드 환경에서 안전하게 사용할 수 있습니다.
4. 동기화된 컬렉션 (Synchronized Collections)
Java Collection Framework는 Collections.synchronized* 메서드를 통해 동기화된 컬렉션을 제공합니다. 이러한 컬렉션은 멀티스레드 환경에서 안전하게 사용할 수 있습니다.
- synchronizedList: 동기화된 List를 반환합니다.
- synchronizedSet: 동기화된 Set를 반환합니다.
- synchronizedMap: 동기화된 Map을 반환합니다.
5. 동시성 컬렉션 (Concurrent Collections)
Java는 java.util.concurrent 패키지를 통해 동시성 컬렉션을 제공합니다. 이들 컬렉션은 높은 성능과 스레드 안전성을 제공합니다.
주요 구현 클래스
- ConcurrentHashMap: 동시성 해시 맵을 구현하여 높은 성능과 스레드 안전성을 제공합니다. 세그먼트 락을 사용하여 성능을 최적화합니다.
- CopyOnWriteArrayList: 읽기 작업이 많고 쓰기 작업이 적은 환경에서 사용하기 적합한 동시성 List를 제공합니다. 쓰기 작업 시 전체 복사본을 만듭니다.
- CopyOnWriteArraySet: CopyOnWriteArrayList를 기반으로 하는 동시성 Set을 제공합니다.
결론
Java Collection Framework는 다양한 데이터 구조와 알고리즘을 제공하여 효율적인 데이터 관리와 조작을 가능하게 합니다. List, Set, Map 인터페이스는 각각 고유한 특성과 용도를 가지며, 동기화된 컬렉션과 동시성 컬렉션은 멀티스레드 환경에서의 안전한 사용을 보장합니다. 이를 통해 개발자는 상황에 맞는 최적의 컬렉션을 선택하여 사용할 수 있습니다.
이런 것들 저번에 학습해서 다른 더 깊은 내용이나 필요한 것들 없어? 알려줘
물론입니다. Java Collection Framework의 더 깊은 내용과 고급 기능들에 대해 탐구해보겠습니다. 다음은 고급 주제와 컬렉션 사용에 있어 필요한 추가적인 사항들입니다:
1. 커스텀 정렬
Java의 컬렉션은 기본적으로 자연 순서 또는 기본 제공 Comparator를 통해 정렬됩니다. 그러나 사용자 정의 정렬이 필요한 경우 Comparator와 Comparable 인터페이스를 구현하여 맞춤 정렬을 수행할 수 있습니다.
- Comparable: 클래스 자체에서 자연 순서를 정의합니다. 예를 들어, compareTo 메서드를 오버라이드하여 객체의 비교 방법을 정의합니다.
- Comparator: 별도의 비교자를 정의하여 클래스 외부에서 정렬 순서를 정의합니다. compare 메서드를 오버라이드하여 두 객체의 비교 방법을 정의합니다.
import java.util.*;
public class CustomSortExample {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 35));
Collections.sort(people); // Comparable에 따른 정렬
System.out.println(people);
people.sort(new AgeComparator()); // Comparator에 따른 정렬
System.out.println(people);
}
}
class Person implements Comparable<Person> {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person other) {
return this.name.compareTo(other.name);
}
@Override
public String toString() {
return this.name + " (" + this.age + ")";
}
}
class AgeComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
return Integer.compare(p1.age, p2.age);
}
}
2. Immutable 컬렉션
변경 불가능한 컬렉션은 데이터 변경을 방지하여 안전성을 높일 수 있습니다. Java 9부터는 List.of(), Set.of(), Map.of() 등의 메서드를 사용하여 불변 컬렉션을 생성할 수 있습니다.
List<String> immutableList = List.of("a", "b", "c");
Set<String> immutableSet = Set.of("a", "b", "c");
Map<String, String> immutableMap = Map.of("key1", "value1", "key2", "value2");
// immutableList.add("d"); // UnsupportedOperationException 발생
3. Stream API와 컬렉션
Java 8에서 도입된 Stream API는 컬렉션을 효율적으로 처리할 수 있는 방법을 제공합니다. Stream을 사용하여 필터링, 매핑, 축소 등의 작업을 수행할 수 있습니다.
import java.util.*;
import java.util.stream.*;
public class StreamExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
System.out.println(filteredNames); // [Alice]
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(upperCaseNames); // [ALICE, BOB, CHARLIE]
}
}
4. Concurrent Collections의 상세 동작
ConcurrentHashMap과 같은 동시성 컬렉션은 멀티스레드 환경에서의 성능과 안정성을 높이기 위해 세그먼테이션과 락 분할 등의 기법을 사용합니다.
ConcurrentHashMap의 내부 구조
- Segmentation: 전체 맵을 여러 세그먼트로 나누어 각 세그먼트에 락을 적용하여 병렬성을 높입니다.
- Lock Striping: 각 세그먼트에 대한 락을 개별적으로 관리하여 동시에 여러 스레드가 맵을 수정할 수 있게 합니다.
import java.util.concurrent.*;
public class ConcurrentMapExample {
public static void main(String[] args) {
ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.computeIfAbsent("banana", k -> 3); // 이미 존재하므로 업데이트되지 않음
map.computeIfAbsent("cherry", k -> 3); // 존재하지 않으므로 추가됨
map.forEach((key, value) -> System.out.println(key + " = " + value));
}
}
5. BlockingQueue와 비동기 프로그래밍
BlockingQueue 인터페이스는 멀티스레드 환경에서 안전하게 사용할 수 있는 큐를 제공합니다. 생산자-소비자 패턴을 구현할 때 유용합니다.
- ArrayBlockingQueue: 고정된 크기의 배열을 기반으로 하는 큐.
- LinkedBlockingQueue: 크기가 가변적인 연결 리스트를 기반으로 하는 큐.
- PriorityBlockingQueue: 요소를 우선순위에 따라 정렬하여 저장하는 큐.
import java.util.concurrent.*;
public class BlockingQueueExample {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
// 생산자 스레드
new Thread(() -> {
try {
queue.put("Message 1");
queue.put("Message 2");
queue.put("Message 3");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
// 소비자 스레드
new Thread(() -> {
try {
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
}
6. 성능 최적화 및 컬렉션 선택 가이드라인
컬렉션을 선택할 때 성능을 최적화하려면 다음 사항을 고려해야 합니다.
- List vs. Set vs. Map: 요소의 중복 허용 여부와 접근 방법에 따라 선택합니다.
- ArrayList vs. LinkedList: 빈번한 삽입/삭제 작업이 있는 경우 LinkedList를, 랜덤 접근이 많은 경우 ArrayList를 선택합니다.
- HashSet vs. TreeSet: 순서가 중요하지 않다면 HashSet을, 정렬이 필요하다면 TreeSet을 선택합니다.
- HashMap vs. TreeMap: 빠른 검색이 필요하면 HashMap을, 키 정렬이 필요하면 TreeMap을 선택합니다.
- ConcurrentHashMap: 높은 동시성을 요구하는 멀티스레드 환경에서 사용합니다.
Java Collection Framework는 다양한 데이터 구조와 알고리즘을 제공하여 효율적인 데이터 관리를 가능하게 합니다. 이를 통해 개발자는 상황에 맞는 최적의 컬렉션을 선택하여 사용할 수 있습니다.
이번에도 2/3은 아는 내용이야 다른 내용이나 더 깊은 지식없어?
알겠습니다. 이번에는 Java Collection Framework에서 덜 알려졌지만 유용한 기능들과 깊이 있는 지식에 대해 다뤄보겠습니다.
1. NavigableMap과 NavigableSet
Java의 NavigableMap과 NavigableSet 인터페이스는 정렬된 컬렉션에서 추가적인 탐색 기능을 제공합니다. 이들은 TreeMap과 TreeSet에서 구현됩니다.
NavigableMap
NavigableMap은 SortedMap의 서브 인터페이스로, 키 기반 탐색 메서드를 추가로 제공합니다.
- lowerEntry(K key): 주어진 키보다 작은 가장 큰 키를 포함하는 Map.Entry를 반환합니다.
- floorEntry(K key): 주어진 키보다 작거나 같은 가장 큰 키를 포함하는 Map.Entry를 반환합니다.
- ceilingEntry(K key): 주어진 키보다 크거나 같은 가장 작은 키를 포함하는 Map.Entry를 반환합니다.
- higherEntry(K key): 주어진 키보다 큰 가장 작은 키를 포함하는 Map.Entry를 반환합니다.
import java.util.*;
public class NavigableMapExample {
public static void main(String[] args) {
NavigableMap<String, Integer> navMap = new TreeMap<>();
navMap.put("a", 1);
navMap.put("b", 2);
navMap.put("c", 3);
System.out.println(navMap.lowerEntry("b")); // a=1
System.out.println(navMap.floorEntry("b")); // b=2
System.out.println(navMap.ceilingEntry("b")); // b=2
System.out.println(navMap.higherEntry("b")); // c=3
}
}
NavigableSet
NavigableSet은 SortedSet의 서브 인터페이스로, 추가적인 탐색 메서드를 제공합니다.
- lower(E e): 주어진 요소보다 작은 가장 큰 요소를 반환합니다.
- floor(E e): 주어진 요소보다 작거나 같은 가장 큰 요소를 반환합니다.
- ceiling(E e): 주어진 요소보다 크거나 같은 가장 작은 요소를 반환합니다.
- higher(E e): 주어진 요소보다 큰 가장 작은 요소를 반환합니다.
import java.util.*;
public class NavigableSetExample {
public static void main(String[] args) {
NavigableSet<Integer> navSet = new TreeSet<>();
navSet.add(1);
navSet.add(2);
navSet.add(3);
System.out.println(navSet.lower(2)); // 1
System.out.println(navSet.floor(2)); // 2
System.out.println(navSet.ceiling(2)); // 2
System.out.println(navSet.higher(2)); // 3
}
}
2. IdentityHashMap
IdentityHashMap은 키를 비교할 때 equals 메서드 대신 == 연산자를 사용합니다. 이는 두 객체가 동일한지 비교하는 것이 아닌, 동일한 참조를 가리키는지 확인하는 것입니다.
import java.util.*;
public class IdentityHashMapExample {
public static void main(String[] args) {
Map<String, String> map = new IdentityHashMap<>();
String key1 = new String("key");
String key2 = new String("key");
map.put(key1, "value1");
map.put(key2, "value2");
System.out.println(map.size()); // 2, key1과 key2는 다른 객체이므로
}
}
3. WeakHashMap
WeakHashMap은 키에 대한 참조를 WeakReference로 저장합니다. 키에 대한 강한 참조가 없으면 가비지 컬렉션에 의해 자동으로 제거될 수 있습니다.
import java.util.*;
public class WeakHashMapExample {
public static void main(String[] args) {
Map<Object, String> map = new WeakHashMap<>();
Object key = new Object();
map.put(key, "value");
System.out.println(map.size()); // 1
key = null;
System.gc(); // 가비지 컬렉터 실행
// 약간의 시간 지연 후 확인 필요
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(map.size()); // 0, 키가 가비지 컬렉션됨
}
}
4. EnumMap
EnumMap은 열거형(enum) 타입을 키로 사용하는 맵입니다. 이는 모든 키가 열거형의 값으로 제한되어 있어 매우 효율적으로 동작합니다.
import java.util.*;
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
public class EnumMapExample {
public static void main(String[] args) {
EnumMap<Day, String> map = new EnumMap<>(Day.class);
map.put(Day.MONDAY, "Start of work week");
map.put(Day.FRIDAY, "End of work week");
for (Map.Entry<Day, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
5. ConcurrentSkipListMap과 ConcurrentSkipListSet
이들은 SkipList 알고리즘을 기반으로 한 동시성 컬렉션입니다. ConcurrentSkipListMap과 ConcurrentSkipListSet은 멀티스레드 환경에서 높은 성능과 확장성을 제공합니다.
import java.util.concurrent.*;
public class ConcurrentSkipListMapExample {
public static void main(String[] args) {
ConcurrentSkipListMap<String, Integer> map = new ConcurrentSkipListMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
map.forEach((key, value) -> System.out.println(key + " = " + value));
}
}
6. 큐와 디큐 (Queue and Deque)
Queue 인터페이스는 FIFO(First-In-First-Out) 방식의 자료 구조를 제공하며, Deque 인터페이스는 양방향 큐를 제공합니다. Deque는 ArrayDeque와 LinkedList로 구현됩니다.
ArrayDeque
ArrayDeque는 크기 제한이 없는 배열 기반의 덱으로, 스택 및 큐 모두로 사용할 수 있습니다.
import java.util.*;
public class ArrayDequeExample {
public static void main(String[] args) {
Deque<String> deque = new ArrayDeque<>();
deque.add("one");
deque.addFirst("zero");
deque.addLast("two");
System.out.println(deque); // [zero, one, two]
}
}
LinkedList as Deque
LinkedList는 Deque 인터페이스를 구현하며, 양방향 연결 리스트로 동작합니다.
import java.util.*;
public class LinkedListDequeExample {
public static void main(String[] args) {
Deque<String> deque = new LinkedList<>();
deque.add("one");
deque.addFirst("zero");
deque.addLast("two");
System.out.println(deque); // [zero, one, two]
}
}
7. PriorityQueue
PriorityQueue는 우선순위에 따라 요소를 정렬하여 처리하는 큐입니다. 내부적으로 힙(Heap) 자료 구조를 사용합니다.
import java.util.*;
public class PriorityQueueExample {
public static void main(String[] args) {
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.add(3);
pq.add(1);
pq.add(2);
while (!pq.isEmpty()) {
System.out.println(pq.poll()); // 1, 2, 3 순으로 출력
}
}
}
이처럼 Java Collection Framework는 다양한 고급 기능을 제공하여 효율적인 데이터 관리를 가능하게 합니다. 이를 통해 개발자는 특정 요구 사항에 맞는 최적의 컬렉션을 선택하고 활용할 수 있습니다.