JavaRush /Курсы /JAVA 25 SELF /Новые коллекции: SequencedCollection, SequencedSet, Seque...

Новые коллекции: SequencedCollection, SequencedSet, SequencedMap

JAVA 25 SELF
34 уровень , 4 лекция
Открыта

1. Проблема порядка в классических коллекциях

В Java всегда были коллекции, которые гарантируют порядок элементов (например, ArrayList, LinkedList, LinkedHashSet, LinkedHashMap), и такие, где порядок не гарантирован (например, HashSet, HashMap). Но у всех этих коллекций был один общий недостаток: несмотря на то, что некоторые из них хранят элементы в определённом порядке, стандартные интерфейсы (List, Set, Map) не предоставляли универсальных методов для доступа к первым и последним элементам или для разворота порядка.

Например, если у вас есть List<String>, вы можете получить первый элемент через list.get(0), но для Set или Map такой трюк уже не сработает — придётся использовать итераторы или писать лишний код. Согласитесь, это не очень удобно и не способствует читаемости.

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

Появление SequencedCollection и связанных интерфейсов

В Java 21 появились новые интерфейсы коллекций:

  • SequencedCollection<E>
  • SequencedSet<E>
  • SequencedMap<K, V>

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

Что это такое?

  • SequencedCollection — это коллекция, в которой элементы имеют определённый порядок, и вы можете легко получить первый, последний элемент, а также развернуть порядок.
  • SequencedSet — то же самое, но для множеств (уникальные элементы).
  • SequencedMap — то же самое, но для отображений (ключ-значение).

Какие коллекции теперь их реализуют?

В Java 21 новые интерфейсы реализуют следующие стандартные коллекции:

  • ArrayList, LinkedListSequencedCollection
  • LinkedHashSet, TreeSetSequencedSet
  • LinkedHashMap, TreeMapSequencedMap

Это значит, что если вы уже используете эти коллекции, вы автоматически получаете новые возможности!

2. Основные методы SequencedCollection, SequencedSet, SequencedMap

Методы SequencedCollection

E getFirst();      // Получить первый элемент
E getLast();       // Получить последний элемент
SequencedCollection<E> reversed(); // Получить коллекцию в обратном порядке

Методы SequencedSet

Те же методы, что и у SequencedCollection, плюс всё, что делает Set.

Методы SequencedMap

Map.Entry<K, V> firstEntry();    // Первый элемент (ключ-значение)
Map.Entry<K, V> lastEntry();     // Последний элемент (ключ-значение)
SequencedMap<K, V> reversed();   // Map в обратном порядке

3. Примеры использования новых интерфейсов

Пример 1: Получение первого и последнего элемента

import java.util.*;

public class SequencedDemo {
    public static void main(String[] args) {
        SequencedCollection<String> sc = new ArrayList<>();
        sc.add("Java");
        sc.add("Python");
        sc.add("Kotlin");

        // Получить первый и последний элемент
        String first = sc.getFirst(); // "Java"
        String last = sc.getLast();   // "Kotlin"

        System.out.println("Первый: " + first);
        System.out.println("Последний: " + last);
    }
}

Вывод:

Первый: Java
Последний: Kotlin

Пример 2: Разворот коллекции

SequencedCollection<String> sc = new LinkedList<>();
sc.add("A");
sc.add("B");
sc.add("C");

SequencedCollection<String> reversed = sc.reversed();
System.out.println(reversed); // [C, B, A]

Обратите внимание: reversed() возвращает представление коллекции в обратном порядке. Если изменить исходную коллекцию, изменится и reversed-представление!

Пример 3: Работа с SequencedSet

SequencedSet<Integer> set = new LinkedHashSet<>();
set.add(100);
set.add(200);
set.add(300);

System.out.println("Первый элемент: " + set.getFirst()); // 100
System.out.println("Последний элемент: " + set.getLast()); // 300

SequencedSet<Integer> reversedSet = set.reversed();
System.out.println(reversedSet); // [300, 200, 100]

Пример 4: Работа с SequencedMap

SequencedMap<String, Integer> map = new LinkedHashMap<>();
map.put("apple", 5);
map.put("banana", 3);
map.put("cherry", 7);

Map.Entry<String, Integer> first = map.firstEntry();
Map.Entry<String, Integer> last = map.lastEntry();

System.out.println("Первый: " + first.getKey() + " = " + first.getValue()); // apple = 5
System.out.println("Последний: " + last.getKey() + " = " + last.getValue()); // cherry = 7

SequencedMap<String, Integer> reversedMap = map.reversed();
System.out.println(reversedMap); // {cherry=7, banana=3, apple=5}

4. Как это связано с вашим приложением?

Допустим, в вашем учебном приложении вы храните список пользователей, которые вошли в систему, и хотите быстро получить первого и последнего пользователя (например, для показа «кто первый вошёл» и «кто последний»). Раньше приходилось писать что-то вроде:

List<String> users = new ArrayList<>();
// ... добавляем пользователей
String first = users.get(0);
String last = users.get(users.size() - 1);

Но если коллекция — это не список, а, например, LinkedHashSet (где элементы уникальны и порядок сохранён), такой трюк уже не сработает:

Set<String> users = new LinkedHashSet<>();
// ... добавляем пользователей
// Как получить первого? Только через итератор:
String first = users.iterator().next();
// А последнего? Только перебрать все элементы!

Теперь всё проще и универсальнее:

SequencedSet<String> users = new LinkedHashSet<>();
// ... добавляем пользователей
String first = users.getFirst();
String last = users.getLast();

Это не только уменьшает количество кода, но и делает его более читаемым и безопасным.

5. Схема новых интерфейсов

classDiagram
    Collection <|-- SequencedCollection
    List <|-- SequencedCollection
    Set <|-- SequencedSet
    Map <|-- SequencedMap
    SequencedCollection <|-- SequencedSet
    SequencedSet <|-- LinkedHashSet
    SequencedSet <|-- TreeSet
    SequencedCollection <|-- ArrayList
    SequencedCollection <|-- LinkedList
    SequencedMap <|-- LinkedHashMap
    SequencedMap <|-- TreeMap

6. Полезные нюансы

Практические преимущества SequencedCollection

  • Единый интерфейс для работы с порядком: Больше не нужно помнить, где есть get(0), где нужен итератор, а где вообще нельзя получить первый элемент.
  • Удобство при работе с очередями и стеками: Легко получить первый (head) и последний (tail) элемент.
  • Безопасность и читаемость: Меньше ошибок, связанных с неправильным использованием коллекций; код становится самодокументируемым.
  • Быстрое разворачивание коллекции: Метод reversed() позволяет легко получить обратный порядок без ручных манипуляций.
  • Простота поддержки и расширения кода: Если в будущем вы захотите заменить, например, ArrayList на LinkedHashSet, код, использующий SequencedCollection, не придётся переписывать.

Особенности реализации

  • Не все коллекции реализуют SequencedCollection: Например, HashSet и HashMap не гарантируют порядок, поэтому не реализуют новые интерфейсы.
  • Методы могут выбрасывать исключения: Если коллекция пуста, вызов getFirst() или getLast() приведёт к NoSuchElementException. Не забудьте проверять, что коллекция не пуста!
  • reversed() — это представление, а не копия: Изменения исходной коллекции отражаются в развёрнутом представлении и наоборот.
  • Совместимость: Новые интерфейсы доступны только начиная с Java 21. Если вы используете более старую версию Java, эти возможности вам пока не доступны (но это отличный повод обновиться!).
  • Generics: Все новые интерфейсы полностью поддерживают generics, так что можно работать с любыми типами данных.

7. Типичные ошибки при работе с SequencedCollection

Ошибка №1: Ожидание поддержки порядка от коллекций, которые его не гарантируют. Если вы попытаетесь привести HashSet к SequencedSet, получите ошибку компиляции — у HashSet нет порядка, и он не реализует этот интерфейс.

Ошибка №2: Игнорирование пустых коллекций. Вызов getFirst() или getLast() на пустой коллекции выбросит исключение. Прежде чем обращаться к этим методам, проверьте, что коллекция не пуста:

if (!sc.isEmpty()) {
    String first = sc.getFirst();
}

Ошибка №3: Недоразумения с reversed(). Метод reversed() возвращает представление, а не копию. Если вы измените reversed-представление, изменится и исходная коллекция (и наоборот). Это может привести к неожиданным результатам, если вы не ожидаете такого поведения.

Ошибка №4: Использование новых интерфейсов на старых версиях Java. Если ваш проект компилируется с версией ниже Java 21, компилятор не найдёт эти интерфейсы. Проверьте версию JDK в настройках проекта!

1
Задача
JAVA 25 SELF, 34 уровень, 4 лекция
Недоступна
Умный диспетчер задач: проверка на пустоту перед работой 🤖
Умный диспетчер задач: проверка на пустоту перед работой 🤖
1
Задача
JAVA 25 SELF, 34 уровень, 4 лекция
Недоступна
Алгоритм шагов: прямое и обратное управление 🔄
Алгоритм шагов: прямое и обратное управление 🔄
1
Опрос
Современные коллекции, 34 уровень, 4 лекция
Недоступен
Современные коллекции
Современные коллекции и неизменяемость
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ