3 ноября 2023
Тэги: Collections, Java, алгоритмы, ООП.
В Java 21 появилась новая группа интерфейсов коллекций, самым основным из которых является SequencedCollection. Он расширяет базовый интерфейс Collection, добавляя в него ряд полезных методов для манипуляций с первым и последним элементами, а также для инвертирования коллекции:
Две основных реализации интерфейса List (ArrayList и LinkedList) также поддерживают этот интерфейс.
С помощью методов addFirst() и addLast() мы можем добавлять элементы в начало и конец списка. По сути метод addLast() эквивалентен часто используемому методу add():
Методы getFirst() и getLast() позволяют просматривать первый и последний элементы списка. Но если список пустой, то мы получим исключение NoSuchElementException:
Так же легко мы можем вывести содержимое списка в обратном порядке с помощью метода reversed():
Удаление элементов теперь возможно сходным образом, но вызывать их можно опять же только тогда, когда есть хотя бы один элемент в списке.
При попытке удалить элемент из пустого списка получаем исключение.
Теперь пара слов о производительности. Получив столь удобные методы, легко забыть, какие действия происходят «под капотом».
Операции чтения первого и последнего элемента, а также операции добавления и удаления в конец списка в обоих реализациях ArrayList и LinkedList происходят мгновенно. А вот добавление и удаление первого элемента в ArrayList гораздо медленее, чем в LinkedList. Это связано с их внутренней реализацией. Более подробно про эти структуры данных см. в статье Коллекции: list, set, map.
Добавление в начало с помощью addFirst() в LinkedList требует изменение пары-тройки ссылок (все элементы остаются на своих местах, а слева к ним присоединяется ещё один), что происходит мгновенно. Тогда как в ArrayList нам нужно сначала сдвинуть ВСЕ элементы массива вправо, чтобы появилось место для первого элемента. Эта операция хорошо оптимизирована, но для больших списков (миллионы элементов) может вносить уже заметную задержку.
То же относится и к removeFirst() в ArrayList. Мы должны сдвинуть ВСЕ элементы влево, начиная со второго.
Поэтому если часто добавляете или удаляете первый элемент – выбирайте LinkedList.
Частным случаем SequencedCollection является интерфейс SequencedSet. При этом он лишь переопределяет унаследованный метод reversed(), возвращая не SequencedCollection, а сам SequencedSet.
Этот интерфейс реализуют классы LinkedHashSet (сохраняет порядок добавления) и TreeSet (сортирует в алфавитном порядке). При этом HashSet данный интерфейс не поддерживает, т.к. не обеспечивает порядок.
В остальном всё, что мы рассмотрели выше для списков, справедливо и для множеств:
В отличие от LinkedHashSet, класс TreeSet хоть и поддерживает интерфейс SequencedSet, но не поддерживает методы addFirst() и addLast(). Вместо них по-прежнему добавляем элементы через add(). И это логично, т.к. TreeSet сам решает какой элемент должны быть «первым» и какой «последним».
При этом удаление первого и последнего элемента из TreeSet возможно:
Мы рассмотрели интерфейс SequencedCollection и его наиболее распространённые реализации ArrayList и LinkedList. Научились добавлять и удалять первый и последний элементы, а также инвертировать упорядоченную коллекцию. Узнали какая реализация лучше подходит для работы с методом addFirst().
Также познакомились с более частным интерфейсом SequencedSet и особенностями его реализации в классах LinkedHashSet и TreeSet.
Kotlin, Java, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, Linux, Hibernate, Collections, Stream API, многопоточность, файлы, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.