В Collection Framework добавлены три новых интерфейса.

Работа с коллекциями улучшается с Java 21, поскольку три новых интерфейса встраиваются прямо в существующие иерархии типов. Эти последовательные коллекции дают нам единый API для доступа к первому и последнему элементам и обработки коллекций в обратном порядке.

Чтобы лучше понять, что такое последовательные коллекции, давайте посмотрим, что такое коллекции раньше.

Платформа коллекций Java

Представленная в Java 1.2 Структура коллекций позволила использовать множество интерфейсов и классов для представления групп объектов. Этот унифицированный API имеет много преимуществ, таких как обеспечение взаимодействия между несвязанными API со стандартными типами коллекций и, следовательно, содействие повторному использованию кода.

Более дюжины интерфейсов обеспечивают превосходную абстракцию для базовых реализаций общего и специального назначения, включая реализацию для параллельных сред. Несмотря на то, что иерархия типов и функциональность различных типов коллекций хорошо продуманы и обеспечивают большую гибкость и функциональность, определенный аспект отсутствует… до сих пор!

Спереди назад, Сзади вперед

Довольно распространенная задача с коллекциями — получить первый или последний элемент, и обычно это некрасиво… Я почти уверен, что каждый, кто читает эту статью, делал следующее бесчисленное количество раз:

List<String> items = ...;

String first = items.get(0);
String last = items.get(items.size() - 1);

Некоторые типы коллекций поддерживают прямой доступ к первому и последнему элементу, но общего интерфейса с общим API (пока) нет:

Type      | First Element     | Last Element
--------- | ----------------- | --------------------------
List      | list.get(0)       | list.get(list.size() - 1)
Deque     | deque.getFirst()  | deque.getLast()
SortedSet | sortedSet.first() | sortedSet.last()

Каждый тип, основанный на Collection, легко перемещается от начала до конца, так как он является потомком Iterator. Таким образом, мы можем использовать for-each циклов, Stream конвейеров и создавать массивы из любой коллекции, вызывая toArray(). Для обратного направления, сзади наперед, нет простого способа или хитрости. Это означает, например, что если нам приходится иметь дело с LinkedHashSet, нам нужно пройти всю коллекцию, чтобы добраться до последнего элемента.

До Java 21 и секвенированных коллекций!

Типы секвенированных коллекций

JEP 431 вводит три новых интерфейса в иерархию типов структуры коллекций на стратегических позициях. Как и многие другие функции за эти годы, эти интерфейсы внедряют свои новые функции с помощью методов по умолчанию, поэтому все по-прежнему обратно совместимо, и никто не обязан реализовывать методы самостоятельно, если только нам не нужна более специализированная реализация.

Три новых последовательных интерфейса:

  • SequencedCollection<E> extends Collection<E>
  • SequencedSet<E> extends SequencedCollection<E>, Set<E>
  • SequencedMap<K, V> extends Map<K, V>

SequencedCollection

Интерфейс SequencedCollection<E> выглядит так, как и следовало ожидать, тем более что все его методы, за исключением reversed(), были продвинуты из ранее существовавшего типа Deque<E>, чтобы обеспечить известный и унифицированный API:

interface SequencedCollection<E> extends Collection<E> {

    // NEW METHOD

    SequencedCollection<E> reversed();

    // PROMOTED METHODS FROM Deque<E>

    void addFirst(E);
    void addLast(E);

    E getFirst();
    E getLast();

    E removeFirst();
    E removeLast();
}

Методы add... и remove... являются необязательными и выдают UnsupportedOperationException в своей реализации по умолчанию для поддержки неизменяемых коллекций. Методы get... ведут себя так же, как и их собратья, выдавая NoSuchElementException в случае пустой коллекции.

SequencedSet

SequencedSet<E> основан на SequencedCollection<E>, но имеет ковариантное переопределение типа возвращаемого значения метода reversed():

interface SequencedSet<E> extends SequencedCollection<E>, Set<E> {
    SequencedSet<E> reversed();
}

Упомянутое ранее поведение также допустимо, так как ни одна из реализаций по умолчанию из SequencedCollection<E> не переопределена.

SequencedMap‹K, V›

Хотя концептуально они отличаются от других коллекций конструкциями, основанными на ключе-значении, последовательный подход все же дает много преимуществ. Как и в случае с SequencedCollection<E>, тип SequencedMap<K, V> получает часть своей функциональности за счет продвижения методов из ранее существовавшего типа, в данном случае NavigableMap<K, V>:

interface SequencedMap<K,V> extends Map<K,V> {

    // NEW METHODS

    SequencedMap<K,V> reversed();

    SequencedSet<K> sequencedKeySet();
    SequencedCollection<V> sequencedValues();
    SequencedSet<Entry<K,V>> sequencedEntrySet();

    V putFirst(K, V);
    V putLast(K, V);


    // PROMOTED METHODS FROM NavigableMap<K, V>
    
    Entry<K, V> firstEntry();
    Entry<K, V> lastEntry();
    
    Entry<K, V> pollFirstEntry();
    Entry<K, V> pollLastEntry();
}

Интересным дополнением являются два продвинутых метода poll..., так как они дают нам простой способ получить доступ к первой или последней записи И удалить ее одним махом.

Расширение Java Collection Framework

Как упоминалось ранее, три новых типа встраиваются прямо в ранее существовавшую иерархию типов, чтобы дать нам все новые преимущества без какого-либо нарушения совместимости:

Типы на основе Collection изменяются следующим образом:

  • И List, и Deque теперь совместно используют SequencedCollection в качестве своего непосредственного суперинтерфейса.
  • SortedSet теперь является прямым потомком SequencedSet
  • Реализация LinkedHashSet теперь также является SequencedSet. дополнительно реализует SequencedSet

Изменения для Map были не столь многочисленны, тип SequenceMap находится прямо под Map и выше новый супер-интерфейс для SorterMap и дополнительный интерфейс для реализации LinkedHashMap.

Чтобы соответствовать общей теме Collections Framework, в Collections также доступны три новых вспомогательных метода static:

  • Collections.unmodifiableSequencedCollection(sequencedCollection)
  • Collections.unmodifiableSequencedSet(sequencedSet)
  • Collections.unmodifiableSequencedMap(sequencedMap)

Заключение

На мой взгляд, введение четко определенного порядка встреч и единого для всех API является долгожданным дополнением к Java. Это предоставит разработчикам более простой способ упростить задачи сбора общих ресурсов, и постепенно Java добавляет больше удобства к своим типам.

Как я уже говорил в предыдущих статьях, он может быть не таким кратким и всеобъемлющим по сравнению с другими языками, но реализация его прямым и обратно совместимым способом — это компромисс, на который я с радостью соглашаюсь.

Заинтересованы в использовании функциональных концепций и методов в коде Java? Ознакомьтесь с моей книгой Функциональный подход к Java!

Моя книга Функциональный подход к Java!

Ресурсы