-
[23.12.29] Java 21 - Sequenced Collections블로그 번역 2023. 12. 29. 15:04반응형
이번 주제는 Java 21에 새로운 기능인 Sequenced Collections 에 관한 주제입니다.
출현 배경
list.add(0, "hello"); list.get(0); list.get(list.size() - 1);
위 예시처럼 컬렉션을 사용하고 있다.
그러나 마지막 또는 첫 번째 요소를 가져오는데 불편한 부분이 있었다.
또 역순으로 가져오기 위해서는 Collections 클래스나 다른 방법을 이용해야한다.
위 불편함을 개발자 친화적으로 만들기 위해서 Java 21 부터는 Sequenced Collection 이 탄생했다.
Sequenced Collection
Sequenced collections, Sequenced set, Sequenced maps 3가지가 추가되었다.
예를 들어 자주 사용하는 List를 보자.
<Java 21 전>
public interface List<E> extends Collection<E> {
<Java 21 후>
public interface List<E> extends SequencedCollection<E> {
상속 클래스가 변경되면서 디폴트 메서드가 추가되었다.
각 메서드는 List 인터페이스에 오버라이드하여 List에 맞게 사용되어지고 있다.
public interface SequencedCollection<E> extends Collection<E> { SequencedCollection<E> reversed(); default void addFirst(E e) { throw new UnsupportedOperationException(); } default void addLast(E e) { throw new UnsupportedOperationException(); } default E getFirst() { return this.iterator().next(); } default E getLast() { return this.reversed().iterator().next(); } default E removeFirst() { var it = this.iterator(); E e = it.next(); it.remove(); return e; } default E removeLast() { var it = this.reversed().iterator(); E e = it.next(); it.remove(); return e; } }
주의 사항은 reverse 메서드는 새로운 인스턴스를 반환하지 않는다.
따라서 list를 수정하는 경우 reversed list도 영향을 받는다.
public static void main(String[] args) { List<String> list = new ArrayList<>(); list.addFirst("hello1"); list.addFirst("hello2"); list.addFirst("hello3"); SequencedCollection<String> reversed = list.reversed(); list.removeFirst(); System.out.println(list); //[hello2, hello1] System.out.println(reversed); //[hello1, hello2] }
예시로 LinkedHashSet 도 보자.
<Java 21 전>
public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, java.io.Serializable {
<Java 21 후>
public class LinkedHashSet<E> extends HashSet<E> implements SequencedSet<E>, Cloneable, java.io.Serializable {
SequencedSet = reversed() + Set 이라고 보면 된다. LinkedHashset은 순서를 보장해준다.
그러나 아래와 같이 사용하면 reversed()를 사용할 수 없다.
public static void main(String[] args) { Set<String> set = new LinkedHashSet<>(); set.add("hello1"); set.add("hello2"); set.add("hello0"); System.out.println(set); }
사용하기 위해선 아래와 같이 사용하자. Set은 SequnencedSet을 상속받고 있지 않기 때문이다.
그리고 동일하게 reversed에 영향을 받는다. 또 만약 아래 상태에서 추가를하게 된다면 reversed와 기존 set요소의 순서가 바뀔 수 있다.
결과를 참고하자.
public static void main(String[] args) { SequencedSet<String> set = new LinkedHashSet<>(); set.add("hello1"); set.add("hello2"); set.add("hello3"); SequencedSet<String> reversed = set.reversed(); reversed.add("test"); System.out.println(set); //[hello1, hello2, hello3, test] System.out.println(reversed); //[test, hello3, hello2, hello1] }
마지막으로 SequencedMap을 살펴보자.
public interface SequencedMap<K, V> extends Map<K, V> { SequencedMap<K, V> reversed(); default Map.Entry<K,V> firstEntry() { var it = entrySet().iterator(); return it.hasNext() ? new NullableKeyValueHolder<>(it.next()) : null; } default Map.Entry<K,V> lastEntry() { var it = reversed().entrySet().iterator(); return it.hasNext() ? new NullableKeyValueHolder<>(it.next()) : null; } default Map.Entry<K,V> pollFirstEntry() { var it = entrySet().iterator(); if (it.hasNext()) { var entry = new NullableKeyValueHolder<>(it.next()); it.remove(); return entry; } else { return null; } } default Map.Entry<K,V> pollLastEntry() { var it = reversed().entrySet().iterator(); if (it.hasNext()) { var entry = new NullableKeyValueHolder<>(it.next()); it.remove(); return entry; } else { return null; } } default V putFirst(K k, V v) { throw new UnsupportedOperationException(); } default V putLast(K k, V v) { throw new UnsupportedOperationException(); } default SequencedSet<K> sequencedKeySet() { class SeqKeySet extends AbstractMap.ViewCollection<K> implements SequencedSet<K> { Collection<K> view() { return SequencedMap.this.keySet(); } public SequencedSet<K> reversed() { return SequencedMap.this.reversed().sequencedKeySet(); } public boolean equals(Object other) { return view().equals(other); } public int hashCode() { return view().hashCode(); } } return new SeqKeySet(); } default SequencedCollection<V> sequencedValues() { class SeqValues extends AbstractMap.ViewCollection<V> implements SequencedCollection<V> { Collection<V> view() { return SequencedMap.this.values(); } public SequencedCollection<V> reversed() { return SequencedMap.this.reversed().sequencedValues(); } } return new SeqValues(); } default SequencedSet<Map.Entry<K, V>> sequencedEntrySet() { class SeqEntrySet extends AbstractMap.ViewCollection<Map.Entry<K, V>> implements SequencedSet<Map.Entry<K, V>> { Collection<Map.Entry<K, V>> view() { return SequencedMap.this.entrySet(); } public SequencedSet<Map.Entry<K, V>> reversed() { return SequencedMap.this.reversed().sequencedEntrySet(); } public boolean equals(Object other) { return view().equals(other); } public int hashCode() { return view().hashCode(); } } return new SeqEntrySet(); } }
여기서 pollFirstEntry()에 대해서 주의할 부분이 있다.
reversed와 reversed 이전 map은 같은 객체이기에 만약 reversed 이후 map에서 pollFirstEntry를 한다면?
그렇다면 기존 map은 pollLastEntry한 것과 동일하다.
public static void main(String[] args) { SequencedMap<String, String> map = new LinkedHashMap<>(); map.put("a1", "hello"); map.put("a2", "hello"); map.put("a3", "hello"); SequencedMap<String, String> reversed = map.reversed(); reversed.pollFirstEntry(); System.out.println(map); //{a1=hello, a2=hello} System.out.println(reversed); //{a2=hello, a1=hello} }
이렇게 간단하게 새로운 Sequenced Collection 에 대해서 알아봤다.
반응형'블로그 번역' 카테고리의 다른 글
[24.03.01] 마이크로서비스 안티 패턴 (0) 2024.03.01 [23.12.31] Java Volatile vs Synchronized (0) 2023.12.31 [23.12.28] 2024년 자바 개발자를 위한 면접 질문 - 1 (0) 2023.12.28 [23.12.27] @Retryable 사용법 (1) 2023.12.27 [23.12.26] Outbox Transaction Pattern (0) 2023.12.26