Collection. Список. ArrayList

Collection. Список. ArrayList


Collection

В основе иерархии ¾ коллекций стоит интерфейс Collection. Именно о нем мы поговорим в первой части сегодняшнего урока.

У Collection есть и предок – Iterable. Но на данном этапе мы его опустим, нам пока не хватит знаний, чтобы использовать предлагаемую им функциональность.

Итак, Collection. Во-первых, стоит отметить, что данный интерфейс является достаточно низкоуровневым – он лишь максимально обобщает функциональность коллекций, независимую от реализации. Именно поэтому его можно иногда увидеть параметром (или даже возвращаемым значением) метода – именно в ситуациях, когда порядок элементов (или его отсутствие), способ устройства и прочие факторы, зависящие от реализации, не играют роли, использование низкоуровневых интерфейсов бывает удобно.

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

  1. int size(). Возвращает количество элементов в коллекции;
  2. boolean isEmpty(). Возвращает true, если коллекция не содержит элементов (пуста);
  3. boolean contains(Object o). Возвращает true, если переданный параметром объект содержится в коллекции (содержание определяется по equals());
  4. boolean containsAll(Collection<?> c). Аналогично предыдущему, но true вернется только если каждый из элементов переданной параметром коллекции содержится в исходной коллекции;
  5. Object[] toArray(). Возвращает содержимое коллекции в виде массива. Обратите внимание, именно массива объектов. Есть перегруженные параметризованные методы, возвращающие массив конкретного типа;
  6. boolean add(E e). Добавляет элемент в коллекцию. Возвращает true, если добавление прошло успешно (понятие успешности зависит от реализации коллекции);
  7. boolean addAll(Collection<? extends E> c). По аналогии с предыдущим методом, добавляет все элементы из коллекции, переданной параметром. Возвращаемое значение малоинформативно – зачастую вернется true, если был добавлен хотя бы один новый элемент;
  8. boolean remove(Object o). Удаляет переданный объект из коллекции. Опять же, сравнение элементов происходит по equals();
  9. boolean removeAll(Collection<?> c). Удаляет из исходной коллекции все элементы, которые содержатся в коллекции, переданной параметром;
  10. boolean retainAll(Collection<?> c). Действие метода обратно предыдущему: удалит из исходной коллекции все элементы, которые НЕ содержатся в коллекции, переданной параметром.
  11. void clear(). Удаляет все элементы из коллекции.

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

На этом первичное знакомство с интерфейсом Collection можно считать успешно пройденным.


List. Как массив, только круче

Первый вид коллекций, с которым мы познакомимся – список (List). Его характерной особенностью является то, что мы можем получить доступ к каждому элементу по его индексу – номеру внутри структуры данных. С поправкой на то, что мы можем вставлять элементы в любое место списка (не только в конец), индекс можно назвать порядковым номером элемента. Нумерация элементов списка, как и в случае с массивом, начинается с 0 (нуля).

Стоит понимать, что хоть список и похож в плане применения и характерных особенностей на массив (или массив на стероидах), далеко не всегда между списком и массивом можно поставить знак равенства. Способ хранения элементов и получение доступа к ним в разных реализациях может сильно отличаться от подобных операций в массиве.

Для знакомства с методами списка (не все из них полезны нам сейчас, но что поделать) и в качестве примера использования – предлагаю познакомиться со статьей: https://metanit.com/java/tutorial/5.2.php

Также в ней затрагивается ArrayList – наиболее распространенная реализация списка. О которой мы поговорим чуть ниже.

Итак, на какие методы списка я рекомендую обратить внимание (кроме уже разобранных методов Collection):

  • void sort(Comparator<? super E> c). Метод для сортировки списка. Его результатом станет представление элементов внутри списка в отсортированном в соответствии с переданным Comparator’ом виде. Т.е. индексы элементов внутри списка могут измениться после вызова этого метода. С самим интерфейсом Comparator, его близкими родственниками и сферой применения мы познакомимся в одном из ближайших уроков;
  • E get(int index). Метод, возвращающий элемент списка, соответствующий индексу, переданному в параметре метода. Если индекс превышает максимальный (size - 1) – будет выброшено исключение;
  • E set(int index, E element). Заменяет элемент по указанному индексу. Не скажу, что этот метод актуален, но он существует и является характерным именно для списка. Возвращает объект, ранее находившийся по указанному индексу;
  • void add(int index, E element). Добавляет элемент по указанному индексу (в отличии от void add(E element), который добавляет элемент в конце списка). При этом элемент, ранее находившийся по этому индексу, будет сдвинут вперед;
  • E remove(int index). Удаляет элемент по индексу. Возвращает удаленный объект;
  • int indexOf(Object o). Возвращает индекс элемента, переданного параметром. Если такой элемент отсутствует – возвращает -1 (минус один).
  • static <E> List<E> of(E... elements). Статический метод, создающий список с элементами, переданными в параметрах. В данном случае – varargs. Также перегружен для разного (от 0 до 10) количества элементов. Существует похожий метод copyOf(), принимающий параметром коллекцию и возвращающий список, созданный из элементов этой коллекции. Стоит обратить внимание, что список, полученный в результате вызова этих методов – неизменяем. Попытка добавить или удалить элемент в нем приведет к выбросу исключения.


Реализации List. ArrayList

В качестве примеров реализации списка рассмотрим по примеру из каждой категории по потокобезопасности, приведенной в предыдущем уроке:

  1. Непотокобезопасная коллекция – ArrayList;
  2. Потокобезопасная (Legacy) – Vector;
  3. Потокобезопасная (java.util.concurrent) – CopyOnWriteArrayList.

Безусловно, на данном этапе нам мало пользы от знания, является ли коллекция потокобезопасной. Но пусть это будет небольшим заделом на будущее.

Отдельно стоит сказать несколько слов об ArrayList. Из названия можно понять, что это список, реализованный на базе массива. Это именно та реализация List, которую вы будете использовать в 99% случаев (по крайней мере, когда будете создавать список с помощью конструктора).

В одном из ближайших уроков мы также познакомимся с его собратом, которого часто любят вспоминать на собеседованиях – LinkedList’ом – связным списком. Но не сегодня. Ниже постараемся разобрать принцип работы ArrayList.

Итак, ArrayList действительно хранит данные внутри поля elementData, представленном массивом объектов. Обратите внимание – массивом объектов, а не параметризованным массивом.

ArrayList может быть создан через несколько разных конструкторов (представлены в статье metanit выше), один из которых содержит параметр initialCapacity – это параметр, который задает начальный размер массива. Подобную операцию мы делали многократно, в данном случае она скрыта под реализацией класса. Если initialCapacity не указать – по умолчанию будет создан пустой массив ({}). Однако при добавлении элемента(-ов) размер массива будет увеличен.

Де-факто, capacity по умолчанию принято за 10 (при добавлении первого элемента в пустой список именно такого размера массив будет создан, если ранее не была явно указана initialCapacity). В дальнейшем, как только массив заполняется – происходит его увеличение в полтора раза (можете рассмотреть принцип работы метода grow()).

При этом уменьшение количества элементов (посредством удаления) не приводит к уменьшению размера массива. Таким образом при большом количестве удалений может оказаться, что реальный размер массива избыточно велик в сравнении с количеством элементов в нем. Чтобы избежать подобной утечки памяти предусмотрен метод trimToSize(), который пересоздаст массив под количество элементов, существующих в списке на момент вызова. Впрочем, вряд ли вам придется использовать его в реальной жизни.

Также для более детального знакомства с ArrayList могу предложить статью: https://habr.com/ru/post/128269/

Не думаю, что вы найдете там что-то принципиально новое, но сама статья вполне подробная и доходчивая. Хоть и очень старая.


С теорией на сегодня все!

Переходим к практике:

Задача 1

Реализуйте задачу из урока 12, используя ArrayList. Рекомендую максимально расширить функциональность сервиса (не в ущерб здравому смыслу), чтобы посмотреть в работе как можно большее количество методов списка.

Опциональное условие: замените ArrayList на Vector. Изменилось ли что-то в кодовой базе при использовании списка другой реализации?


Задача 2(*)

Попробуйте реализовать собственную коллекцию, наследуясь от Collection. За основу можно взять задачу 4 из урока 28. Если возникнет необходимость в имплементации методов, не рассмотренных в статье - можете реализовать их как заглушки (возвращая null/фиксированное значение по умолчанию/бросая исключение).


Если что-то непонятно или не получается – welcome в комменты к посту или в лс:)

Канал: https://t.me/+relA0-qlUYAxZjI6

Мой тг: https://t.me/ironicMotherfucker

 

Дорогу осилит идущий!

Report Page