Collections

Вопросы к собеседованию

Вопросы: (показать все)

Базовые интерфейсы

В библиотеке коллекций Java существует два базовых интерфейса, реализации которых и представляют совокупность всех классов коллекций:

  1. Collection - коллекция содержит набор объектов (элементов). Здесь определены основные методы для манипуляции с данными, такие как вставка (add, addAll), удаление (remove, removeAll, clear), поиск (contains)
  2. Map - описывает коллекцию, состоящую из пар “ключ — значение”. У каждого ключа только одно значение, что соответствует математическому понятию однозначной функции или отображения.

    Такую коллекцию часто называют еще словарем (dictionary) или ассоциативным массивом (associative array).

    Никак НЕ относится к интерфейсу Collection и является самостоятельным.

Хотя фреймворк называется Java Collections Framework, интерфейс Map и его реализации входят во фреймворк также!

Интерфейсы Collection и Map являются базовыми, но они не есть единственными.

Их расширяют другие интерфейсы, добавляющие дополнительный функционал.


Что такое "коллекция"?

Коллекция это набор объектов (элементов).

Какие данные могут хранить коллекции?

Любые "объектные" типы.

Преимущества использования коллекций по сравнению с массивами?

  • Динамический размер: Коллекции позволяют добавлять и удалять элементы во время выполнения программы, без необходимости заранее определенного размера. Это особенно полезно, когда неизвестно заранее, сколько элементов будет содержаться в коллекции. Напротив, массивы имеют фиксированный размер, который нужно определить при их создании.
  • Удобные методы: Коллекции предоставляют множество полезных методов для работы с элементами, таких как сортировка, поиск, фильтрация и многое другое. Это упрощает манипуляцию с данными и обработку элементов коллекции. В отличие от массивов, где вам нужно написать свои собственные алгоритмы для выполнения таких операций.
  • Гибкость: Коллекции предоставляют различные типы коллекций, такие как списки, множества и отображения, которые подходят для различных сценариев использования.
    Например, список (ArrayList) хорошо подходит для хранения упорядоченных элементов, множество (HashSet) гарантирует уникальность элементов, а отображение (HashMap) используется для хранения пар "ключ-значение".
  • Высокий уровень абстракции: Коллекции представляют собой высокоуровневый инструмент для работы с данными, который скрывает детали реализации и предоставляет простой и понятный интерфейс для работы с данными.
    Например, вам не нужно беспокоиться о выделении памяти или управлении индексами при использовании коллекций.

В целом, использование коллекций в Java предоставляет более гибкий и удобный способ работы с данными, чем использование массивов. Они позволяют динамически изменять размер коллекции, предоставляют полезные методы для манипуляции с данными и поддерживают различные типы коллекций для различных сценариев использования.


Какие есть типы коллекций?
Как они характеризуются?
Расскажите про иерархию коллекций List, Set, Map.

смотри Базовые интерфейсы

Интерфейс Collection

Интерфейс Collection

Интерфейс Collection не является базовым. Он расширяет интерфейс Iterable, у которого есть только один метод iterator().

Это значит что любая коллекция будет возвращать итератор, а также ее можно без всяких трудностей использовать в конструкции foreach.

Итератор – объект, который абстрагирует за единым интерфейсом доступ к элементам коллекции.
Итератор это паттерн позволяющий получить доступ к элементам любой коллекции без вникания в суть ее реализации.

Интерфейс Collection расширяют интерфейсы List, Set и Queue:


Что такое итератор?

Iterator - это интерфейс, предоставляющий функциональность для итерации по любой коллекции Java.
Итераторы позволяют безопасно удалять элементы во время обхода коллекций.

Итератор предоставляет методы для выполнения следующих операций:

  1. hasNext(): Возвращает true, если в коллекции остались элементы для перехода.
    В противном случае он возвращает false.

  2. next(): Возвращает следующий элемент из коллекции. Если таких элементов нет, он вызывает исключение NoSuchElementException.

  3. remove(): Удаляет последний элемент, который был возвращен итератором.
    Этот метод можно вызвать только один раз для каждого вызова next().
    Если метод next() не был вызван, или remove() уже был вызван после последнего вызова next(), этот метод вызывает исключение IllegalStateException.

Важно отметить, что итераторы обеспечивают fail-fast поведение, что означает, что если коллекция изменяется в процессе итерации (кроме использования самого метода remove()), итератор быстро завершится, вызвав ConcurrentModificationException.
Это помогает предотвратить непредсказуемое поведение, возникающее при одновременном изменении коллекции во время итерации.

еще про ListIterator


В чем разница между Iterator и Iterable?

Iterable и Iterator - два различных интерфейса в Java, которые оба играют важную роль в работе с коллекциями данных.

Они тесно связаны, но имеют разные цели:

  • Iterable - это интерфейс, который реализуется любым объектом, который может вернуть Iterator.
    Он определяет единственный метод, iterator(), который возвращает новый Iterator.
    Это позволяет объекту быть целью для "foreach" конструкции.

  • Iterator - это интерфейс, который используется для обхода коллекции.
    Он позволяет последовательно проходить по коллекции, не заботясь о том, как она структурирована внутри.
    Определяет методы hasNext(), next() и remove().

Вот базовая идея:

Если у вас есть коллекция элементов и вы хотите сделать ее доступной для обхода, вы реализуете Iterable на этой коллекции.
Это позволяет другим использовать цикл for-each с вашей коллекцией.

Когда вы хотите обойти коллекцию, вы используете Iterator, который предоставляет коллекция.
Это позволяет вам проверить, есть ли следующий элемент (hasNext()), получить следующий элемент (next()) и удалить текущий элемент (remove()).


В чем разница между Iterator и Enumeration?

Iterator и Enumeration являются двумя концепциями в Java, которые используются для обхода коллекций.

Они оба служат похожим целям, но есть несколько ключевых различий:

  1. Методы: Iterator имеет три метода: hasNext(), next(), и remove(), в то время как Enumeration имеет только два метода: hasMoreElements() и nextElement().
    Метод remove() в Iterator предоставляет возможность удаления элементов из коллекции во время обхода, что Enumeration не может делать.

  2. Безопасность: Iterator является более безопасным, поскольку он не позволяет изменять коллекцию во время обхода, кроме использования метода remove().
    Если коллекция изменяется в процессе итерации (не считая использования remove()), Iterator быстро завершится, вызвав ConcurrentModificationException.
    Enumeration не предоставляет этой защиты.

  3. Использование: Iterator широко используется в новых реализациях Java Collection Framework, в то время как Enumeration используется во многих старых классах, таких как Vector и Properties, и сейчас он считается устаревшим.

  4. Производительность: В то время как Iterator является немного более медленным из-за дополнительной проверки безопасности, он обеспечивает более сильную устойчивость к ошибкам.

В общем, в современном Java программировании рекомендуется использовать Iterator вместо Enumeration из-за его расширенной функциональности и улучшенной безопасности.


Как задается порядок следования объектов в коллекции?
Как отсортировать коллекцию?


Что такое сортировка по принципу Natural Order?


Чем отличается Comparable от Comparator?

Реализации интерфейса List

Интерфейс List

Красным выделены интерфейсы, зеленым – абстрактные классы, а синим готовые реализации. Здесь не вся иерархия, а только основная её часть.

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

ArrayList – пожалуй самая часто используемая коллекция. Он инкапсулирует в себе обычный массив, длина которого может увеличиваться при добавлении новых элементов. Так как ArrayList использует массив, то время доступа к элементу по индексу минимально (В отличии от LinkedList). При удалении произвольного элемента из списка, все элементы находящиеся «правее» смещаются на одну ячейку влево, при этом реальный размер массива (его емкость, capacity) не изменяется. Если при добавлении элемента, оказывается, что массив полностью заполнен, будет создан новый массив размером (n * 3) / 2 + 1, в него будут помещены все элементы из старого массива + новый, добавляемый элемент.

LinkedList – Двусвязный список. Это структура данных, состоящая из узлов, каждый из которых содержит как собственно данные, так и две ссылки («связки») на следующий и предыдущий узел списка. Доступ к произвольному элементу осуществляется за линейное время (но доступ к первому и последнему элементу списка всегда осуществляется за константное время — ссылки постоянно хранятся на первый и последний, так что добавление элемента в конец списка вовсе не значит, что прийдется перебирать весь список в поисках последнего элемента).


В чём отличие ArrayList от LinkedList?

Массив - Двусвязный список


Как происходит удаление элементов из ArrayList?


Как происходит удаление элементов из LinkedList?

Реализации интерфейса Set

Интерфейс Set

HashSet – коллекция, не позволяющая хранить одинаковые объекты (как и любой Set).

HashSet инкапсулирует в себе объект HashMap (то-есть использует для хранения хэш-таблицу).

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

Если Вы хотите использовать HashSet для хранения объектов своих классов, то вы должны переопределить методы hashCode() и equals(), иначе два логически-одинаковых объекта будут считаться разными по хеш-коду, так как при добавлении элемента в коллекцию будет вызываться метод hashCode() класса Object (который скорее-всего вернет разный хэш-код для ваших объектов).

Класс HashSet не гарантирует упорядоченности элементов, поскольку процесс хеширования сам по себе обычно не порождает сортированных наборов. Если нужны сортированные наборы, то лучшим выбором может быть другой тип коллекций - TreeSet.

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

TreeSet – коллекция, которая хранит свои элементы в виде упорядоченного по значениям дерева. TreeSet инкапсулирует в себе TreeMap, который в свою очередь использует сбалансированное бинарное красно-черное дерево для хранения элементов.
TreeSet хорош тем, что для операций add, remove и contains потребуется гарантированное время log(n).


В чём отличие HashSet от TreeSet?

Реализации интерфейса Queue

Интерфейс Queue

PriorityQueue – единственная прямая реализация интерфейса Queue (не считая LinkedList, который больше является списком, чем очередью).

Реализации интерфейса Map

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

Интерфейс Map

HashMap

Реализация основана на хэш-таблицах, реализует интерфейс Map.

Ключи и значения могут быть любых типов, в том числе и null.

Данная реализация не дает гарантий относительно порядка элементов.


LinkedHashMap

Реализация расширяет класс HashMap.

Класс создает связный список элементов в карте, расположенных в том порядке, в котором они вставлялись. Это позволяет организовать перебор карты в порядке вставки.

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

Также можно создать объект класса LinkedHashMap, возвращающий свои элементы в том порядке, в котором к ним в последний раз осуществлялся доступ.


TreeMap

Красно-черное дерево реализующее интерфейс NavigableMap.

Коллекция сортируется по естественному упорядочиванию (natural ordering) ее ключей или используя интерфейс Comparator который задается при создании коллекции.

Эта имплементация гарантирует время доступа log(n) для следующих методов:

containsKey, get, put, и remove


WeakHashMap

Основан на хэш-таблицах, реализует интерфейс Map с так называемыми слабыми ключами (weak keys). Пара в данной коллекции автоматически будет удалена когда ссылка на ключ больше нигде не используется. Другими словами, нахождение объекта представленного ключем в данной коллекции не блокирует сборщик мусора от зачистки. После того как ключ будет зачищен вся пара будет удалена из коллекции.

Другие коллекции

Их еще называют "устаревшими".

Но нет аннотации @Deprecated или каких-либо иных, которые бы запрещали их использование в коде.


Enumeration

Интерфейс. В современной версии Java рекомендуется применять Iterator.


Dictionary

Абстрактный класс, аналог интерфейса Map.

Реализации не имеет, рекомендуют его рассматривать как интерфейс. Наиболее известная реализация – Hashtable.


Hashtable

Класс. Имплементирует классическую структуру данных – хэш таблицу.

В современных версиях Java рекомендуется использовать HashMap.


Vector

Класс. Аналог класса ArrayList.

Поддерживает упорядоченный список элементов, хранимых во "внутреннем" массиве.


Stack

Класс. Производный от Vector.

Добавлены методы "вталкивания" (push) и "выталкивания" (pop) элементов, так что список может трактоваться в терминах, принятых для описания структуры данных стека (stack).



Все методы Hashtable, Stack, Vector являются синхронизированными, что делает их менее эффективными в однопоточных приложениях.

Синхронизированные коллекции

Получить синхронизированные объекты коллекций можно используя статические методы класса Collections: synchronizedMap и synchronizedList

Map m = Collections.synchronizedMap(new HashMap());

List l = Collections.synchronizedList(new ArrayList());

Синхронизированные обрамления коллекций synchronizedMap и synchronizedList иногда называют условно потоко безопасными – все операции в отдельности потокобезопасны, но последовательности операций, где управляющий поток зависит от результатов предыдущих операций, могут быть причиной конкуренции за данные.

Условная безопасность потоков, обеспечиваемая synchronizedList и synchronizedMap представляет скрытую угрозу – разработчики полагают, что, раз эти коллекции синхронизированы, значит, они полностью потокобезопасны, и пренебрегают должной синхронизацией составных операций. В результате, хотя эти программы и работают при лёгкой нагрузке, но при серьёзной нагрузке они могут начать выкидывать NullPointerException или ConcurrentModificationException.

Кроме того всегда существует возможность "классической" синхронизации с помощью блока synchronized.

loadFactor и capacity

Дополнительно

Можно почитать