블로그 번역

[23.12.29] Java 21 - Sequenced Collections

HOONY_612 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 에 대해서 알아봤다.

 

반응형