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

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

На источники, откуда черпалась информация, предоставлены ссылки в конце статьи.

Оглавление

Основные понятия

Структура данных (англ. Data Structure) — программная единица, позволяющая хранить и обрабатывать однотипные и/или логически связанные данные.
Для добавления, поиска, изменения и удаления данных структура данных предоставляет некоторый набор функций, составляющих её интерфейс.

Структура данных – это контейнер, который хранит данные в организованной форме.

Массив – самая простая и наиболее широко используемая структура.
Коллекции Java Сollections Framework являются производными от массивов.

Java Collections Framework — это набор связанных классов и интерфейсов, реализующих широко используемые структуры данных — Коллекции.
Он был спроектирован и разработан, в первую очередь, Джошуа Блохом.

Массивы

Массив – это структура данных, состоящая из упорядоченных ячеек, расположенных друг за другом в памяти, которые могут хранить в себе элементы только одного, заранее заданного типа.

Может хранить данные примитивных типов, строки (String) и ссылки на объекты класса.

Размер массива задается при его создании.
Тип данных и длину массива в дальнейшем изменить нельзя.

int[] numbers = new int[100];         // массив чисел на 100 ячеек

String[] strings = new String[10];    // массив строк на 10 ячеек

Person[] people = new Person[5];      // массив объектов класса Person   

Элементы массива доступны по индексу ячейки.
Значение по умолчанию для ячеек – null
Отсчет индексов ведется от 0

В памяти элементы массива размещаются в едином блоке.
Это сделано для более эффективного и быстрого доступа к ним.

String[] strings = new String[10];

strings[0] = "Hello";                 // присвоить значение первой ячейке
strings[1] = " world!";               // присвоить значение второй ячейке

System.out.println(strings[0] + strings[1]);    // получить значения по индексу

Быстрая инициализация массива
При быстрой инициализации размер массива не задается – размер будет определен автоматически в соответствии с количеством переданных в массив элементов.

int[] numbers = new int[]{1, 2, 3, 4, 5};

int[] numbers = {1, 2, 3, 4, 5};      // без ключевого слова new

String[] strings = {"Hello", " world!"};

Копирование массива System.arraycopy()
Этот статический метод класса System создан специально для копирования массива.
Копирует массив из указанного исходного массива, начиная с указанной позиции, в указанную позицию целевого массива.

System.arraycopy(array1, 0, array2, 0, array1.length);

Массивы бывают двух типов:

  • Одномерные
    Массив хранит однотипные элементы

  • Многомерные
    Массив хранит ссылки на другие массивы

int[][] numbers = new int[3][];                    // двумерный массив

String[][][] threeDimArray = new String[3][][];    // трехмерный массив

String[][] strings = new String[3][5];	           // таблица 3 столбца 5 строк

Инициализация двумерного массива
Обязательно задать размер только первого массива.
Каждый из внутренних массивов может быть своей длины – указывается при добавлении.

int[][] numbers = new int[3][];

numbers[0] = new int[2];
numbers[1] = new int[4];
numbers[2] = new int[3];

Быстрая инициализация двухмерного массива

int[][] numbers = {{1, 2}, {1, 2, 3}, {1, 2, 3, 4, 5}};

Добавить элемент в двухмерный массив
Сначала указывается индекс ячейки многомерного массива int[][] numbers, а затем индекс ячейки внутреннего массива

int[][] numbers = new int[3][3];

numbers[0][0] = 1;
numbers[1][0] = 2;
numbers[2][0] = 3;

Класс Arrays

Класс Arrays содержит различные статические методы для работы с Массивами (например, сортировка и поиск).

public final class Array extends Object

Сортировка массива Arrays.sort()
Отсортировать можно как весь массив, так его отдельную часть – диапазон индексов.

int[] numbers = {3, 2, 5, 1, 4};

Arrays.sort(numbers);          // [1, 2, 3, 4, 5]

Arrays.sort(numbers, 0, 4);    // [1, 2, 3, 5, 4]

Привести содержимое массива к строке Arrays.toString()
Для строкового представления многомерного массива создан отдельный метод Arrays.deepToString()

String str = Arrays.toString(strings);

String str = Arrays.deepToString(numbers);    // для многомерных массивов

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

Arrays.equals(numbers, numbers2);

Arrays.deepEquals(numbers, numbers2)          // для многомерных массивов

Заполнить массив одинаковыми значениями Arrays.fill()
Заполнить можно как весь массив, так и его часть – диапазон индексов.
Последний индекс не входит в диапазон.
Работает только с одномерными массивами.

Arrays.fill(numbers, 777);           // присвоить всем ячейкам значение 777

Arrays.fill(numbers, 3, 10, 777);    // заполнить ячейки с 3 по 9 индекс

Перевести Массив в List Arrays.asList()

List<Integer> listNumbers = Arrays.asList(arrayNumbers);

List<Integer> listNumbers = Arrays.asList(1, 2, 3);

Копирование массива Arrays.copyOf(), Arrays.copyOfRange()
Если элементы не поместились в новый массив – лишние значения игнорируются.
Если длина нового массива больше – ячейки заполняются значениями по умолчанию.
Под капотом используется метод System.arraycopy()

int[] numbersCopy = Arrays.copyOf(numbers, numbers.length);

int[] numbersCopy = Arrays.copyOf(numbers, 4);          // начать с индекса 4

int[] numbersCopy = Arrays.copyOfRange(numbers, 2, 7);  // индекс 7 НЕ входит

Бинарный поиск Arrays.binarySearch()
Для этого массив предварительного нужно отсортировать Arrays.sort()

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

Возвращает индекс искомого элемента в массиве либо отрицательное число – точку вставки (-(insertion point) - 1)
Точка вставки определяется как точка, в которую ключ будет вставлен в массив.

int[] numbers = {3, 2, 5, 1, 4};

Arrays.sort(numbers);                 // [1, 2, 3, 4, 5]

Arrays.binarySearch(numbers, 4);      // вернет индекс ячейки 3

Arrays.binarySearch(numbers, 6);      // вернет точку вставки -5 - 1 = -6

Collections Framework. Коллекции

Главным ограничением массива является его фиксированная длина.
Эту проблему решают Коллекции – набор интерфейсов и классов для работы с группами объектов.

Коллекции строго структурированы.
В основе любой коллекции лежит использование того или иного интерфейса, который определяет ее базовый функционал.

Два главных интерфейса коллекций – Collection и Map
От них наследуют все другие интерфейсы и классы Collection Framework.
Интерфейс Collection наследует интерфейс Iterable, который позволяет последовательно обходить элементы.

public interface Collection<E> extends Iterable<E>

public interface Map<K,V>

Collection хранит объекты как в массиве: каждый помещается в ячейку по индексу.
В отличие от массивов, не может хранить примитивные типы.

Map – хранит данные в виде пары ключ – значение.
Map
 не наследуется от интерфейса Collection, но входит в состав Collections Framework.

Иерархия Collections Framework

Интерфейс Iterable. Iterator. ListIterator

Все коллекции Collection наследуют интерфейс Iterable .
Метод этого интерфейса iterator() возвращает Итератор над элементами коллекции.

List<String> list = new ArrayList();

Iterator<String> iterator = list.iterator();

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

Методы Iterator

forEachRemaining(Consumer<? super E> action)

Выполняет данное действие для каждого оставшегося элемента до тех пор, пока все элементы не будут обработаны или действие не выдаст исключение.

hasNext()

Возвращает true, если итерация содержит следующий элемент.

next()

Возвращает следующий элемент в итерации.

remove()

Удаляет из базовой коллекции последний элемент, возвращенный этим итератором.

Нельзя одновременно производить итерацию коллекции и удаление ее элементов.
Чтобы удалить элемент - его сначала нужно получить.

while (iterator.hasNext()) {
    Integer next = iterator.next();
    iterator.remove();
    iterator.forEachRemaining(System.out::println);
}

Интерфейс ListIterator
Расширяет Iterator, обеспечивает двунаправленный обход списка List и модификацию элементов. Позволяет обойти коллекцию в обоих направлениях.
Доступен только для коллекций, которые реализуют Listполучаем его с помощью метода интерфейса List listIterator()

List<String> list = Arrays.asList("A", "B", "C", "D", "E");

ListIterator<String> listIterator = list.listIterator();

Методы ListIterator

add(E e)

Вставляет указанный элемент в список перед элементом, который должен быть возвращен следующим вызовом next()

hasNext()

Возвращает true, если этот итератор при обходе списка вперед содержит следующий элемент.

hasPrevious()

Возвращает true, если этот итератор при обходе списка в обратном направлении содержит следующий элемент.

next()

Возвращает следующий элемент в списке и перемещает положение курсора.

nextIndex()

Возвращает индекс элемента, который будет возвращен следующим вызовом метода next()

previous()

Возвращает предыдущий элемент в списке и перемещает положение курсора назад.

previousIndex()

Возвращает индекс элемента, который будет возвращен следующим вызовом метода previous()

remove()

Удаляет из списка последний элемент, который был возвращен next() или previous()

set(E e)

Заменяет последний элемент, возвращаемый методом next() или previous(), указанным элементом.

Изначально курсор итератора находится в начале списка и для того, чтобы сделать итерацию назад – нужно сделать хоть одну итерацию вперед.
Без этого вызов метода hasPrevious() будет возвращать false

while (listIterator.hasNext()) {
    String element = listIterator.next();
    listIterator.set(element + ",");
}

while (listIterator.hasPrevious()) {
    String element = listIterator.previous();
    System.out.print(element + " ");
}

Интерфейс Collection

Интерфейс Collection расширяет интерфейс Iterable – благодаря этому все классы наследники Collection можно перебирать Итератором.

public interface Collection<E> extends Iterable<E>

Этот интерфейс содержит основные методы, для работы с коллекциями.
Их получают все коллекции (за исключением Map).
Поэтому общие принципы работы с коллекциями будут одни и те же.

Основные методы Collection:

add(E item)

Добавление элемента в коллекцию.
При удачном добавлении возвращает true, при неудачном – false

addAll(Collection<?> c)

Добавляет все элементы указанной коллекции в эту коллекцию.

clear() 

Удаляет все элементы из этой коллекции

contains(Object item)

Возвращает true, если эта коллекция содержит указанный элемент.

containsAll(Collection<?> c)

Возвращает true, если эта коллекция содержит все элементы указанной коллекции.

isEmpty()

Возвращает true, если эта коллекция не содержит элементов.

remove(Object item)

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

removeAll(Collection<?> c)

Удаляет все элементы этой коллекции, которые содержатся в указанной коллекции.

retainAll(Collection<?> c)

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

size()

Возвращает количество элементов в этой коллекции.

stream()

Возвращает последовательный поток с этой коллекцией в качестве источника.

toArray()

Возвращает массив, содержащий все элементы этой коллекции.

Интерфейс List

Интерфейс List расширяет Collection.
Используется для создания простых списков.

Элементы списка упорядочены и могут быть доступны по индексу.
Список может содержать повторяющиеся элементы.

public interface List<E> extends Collection<E>

Помимо Iterator списки также могут вернуть ListIterator, который позволяет вставку и замену элементов, а также двунаправленный доступ.

Основные методы List:

add(E element)

Добавляет элемент в конец этого списка.

add(int index, E element)

Добавляет элемент по индексу в этот список. Старые элементы сдвигаются вправо и их индекс увеличивается на 1.

addAll(int index, Collection c)

Вставляет все элементы указанной коллекции в список с указанного индекса.

get(int index)

Возвращает элемент списка по указанному индексу.

set(int index, object obj)

Заменяет элемент по индексу в этом списке на указанный элемент.

indexOf(Object obj)

Возвращает индекс первого указанного элемента в списке или -1, если элемент отсутствует.

lastindexOf(object obj)

Возвращает индекс последнего элемента в этом списке или -1, если элемент отсутствует.

listIterator()

Возвращает итератор списка по элементам этого списка.

listIterator(int index)

Возвращает итератор списка по элементам этого списка, начиная с указанного индекса.

subList(int from, int to)

Возвращает часть коллекции от позиции fromвключительно до позиции to исключительно

toArray()

Возвращает массив, содержащий все элементы этого списка.

Класс ArrayList

ArrayList сохраняет элементы во внутренний массив и управляет его расширением.
По мере добавления элементов емкость внутреннего массива автоматически увеличивается.

public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable

List создается с начальной емкостью capacity 10 ячеек
Переменная size хранит количество добавленных элементов и изначально равна 0
size – это не емкость внутреннего массива – емкость массива нам недоступна.

Конструкторы ArrayList

List<String> list = new ArrayList();

List<String> list2 = new ArrayList(100);    // задаем емкость capacity

List<String> list3 = new ArrayList<>(list);
list.add(" world!");			            // добавить в конец списка
list.add(0, "Hello");				        // добавить по индексу

list.size();				                // сколько добавлено элементов

list.remove("Hello");				        // удалить по значению
list.remove(1);					            // удалить по индексу

ArrayList автоматически расширяется по формуле: (10 * 1,5) + 1

Автоматически внутренний массив не уменьшается.
Чтобы обрезать емкость списка до реального количества элементов в нем – используем метод trimToSize()
Этот метод есть только у ArrayList и отсутсвует у List.

ArrayList list = new ArrayList() {{
    add("Hello");                           // быстрая инициализация
    add(" world!");
}};
        
list.trimToSize();

Основные моменты ArrayList:

  • Использует внутри обычный массив

  • Доступ к элементам по индексу за константное время O(1)

  • Доступ к элементам по значению за линейное время O(n)

  • Хранит любые значения в том числе и null

  • Не синхронизирован

  • Автоматически увеличивается, но не уменьшается

Класс LinkedList

LinkedList использует для хранения двусвязный список.
Помимо интерфейса List  реализует интерфейсы Dequeue и Queue.
Соединяет функциональность работы со списком и фукциональность очереди.

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable
List<String> list = new LinkedList<>();

list.add(" world!");			            // добавить в конец списка
list.add(0, "Hello");				        // добавить по индексу

list.size();				                // количество элементов в списке

list.remove("Hello");				        // удалить по значению
list.remove(1);					            // удалить по индексу

У каждого элемента LinkedList имеется ссылки на предыдущий и следующий элементы списка. Напоминает связанные звенья одной цепи.

По этим ссылкам можно последовательно переходить от одного элемента к другому.
Для установки таких ссылок LinkedList использует объекты своего вложенного класса Node.

private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

У LinkedList есть методы для работы с началом и концом списка, которых нет в ArrayList:

addFirst()

Вставляет элемент в начало списка.

addLast()

Вставляет элемент в конец списка.

removeFirst()

Удаляет и возвращает первый элемент из этого списка.

removeLast()

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

Выгода использования LinkedList в работе с серединой списка.
Вставка и удаление в середину LinkedList устроены гораздо проще, чем в ArrayList: для этого внутри списка просто переопределятся ссылки на соседние элементы.

НО! Все элементы массива ArrayList находятся в одном блоке памяти, и операция по сдвигу элементов массива выполняются быстрым низкоуровневым методом System.arraycopy()

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

Все это делает использование LinkedList редким случаем.

Основные моменты LinkedList:

  • Не синхронизирован

  • Позволяет хранить любые повторяющиеся объекты, в том числе null

  • За константное время O(1) выполняются операции вставки и удаления как первого и последнего элементов, так и элемента из середины списка.

  • Время поиска позиции элемента осуществляется за линейное время O(n)

  • Операции поиска элемента по индексу и по значению выполняются за линейное время O(n)

Интерфейс Map

Map – это массив на 16 buckets (корзин) – от 0 до 15.
Расширяется: 16 – 32 – 64 – и далее переходит к структуре red-black tree.

Хранит данные парами: key – value
Ключ всегда является уникальным.
Доступ к значениям осуществляется по ключу.
Ключ нельзя получить по значению – значения могут быть повторяющимися.

public interface Map<K,V>

Map хорошо сбалансирована, когда хорошо предопределены методы hashcode() у хранимых в ней элементов.

Худший результат работы мапы – Коллизия (когда все элементы попадают в один bucket и их приходится перебирать итерацией).

Изначально, bucket в HashMap представляет из себя связный список LinkedList.
При возникновении Коллизии очередная пара ключ-значение добавляется в этот список.

Если размер связного списка становится равен 8 элементам, происходит преобразование связного списка в дерево.
При этом, найти элемент в HashMap в худшем случае уже можно за O(log(n)), а не за O(n), как в связном списке.

Основные методы Map:

clear()

Удаляет все пары ключ-значение из этой мапы.

containsKey(Object key)

Возвращает true, если эта мапа содержит значение для указанного ключа.

containsValue(Object value)

Возвращает true, если эта мапа сопоставляет один или несколько ключей с указанным значением.

entrySet()

Возвращает Set сопоставлений (пар ключ-значение), содержащихся на этой карте.

get(Object key)

Возвращает значение, с которым сопоставлен указанный ключ, или null

getOrDefault(Object key, V defaultValue)

Возвращает значение, с которым сопоставлен указанный ключ, или defaultValue, если эта мапа не содержит значения для ключа.

isEmpty()

Возвращает true, если эта мапа пустая (не содержит пар ключ-значение).

keySet()

Возвращает Set из ключей, содержащихся в этой маме.

put(K key, V value)

Связывает указанное значение с указанным ключом и кладет это сопоставление в мапу.

putAll(Map<? extends K,? extends V> m)

Копирует все сопоставления с указанной мапы на эту мапу.

putIfAbsent(K key, V value)

Если указанный ключ отсутствует в мапе (или сопоставлен с нулевым значением), связывает его с заданным значением, кладет пару в мапу и возвращает null, в противном случае возвращает текущее значение.

remove(Object key)

Удаляет значение по ключу из этой мапы, если оно присутствует.

remove(Object key, Object value)

Удаляет запись для указанного ключа только в том случае, если она в настоящее время сопоставлена с указанным значением.

replace(K key, V value)

Заменяет значение для указанного ключа только в том случае, если он в настоящее время уже сопоставлен с каким-либо значением.

size()

Возвращает количество сопоставлений ключ-значение в этой мапе.

values()

Возвращает Collection значений, содержащихся на этой карте.

Интерфейс Map.Entry

Вложенный nested интерфейс Map.
Это структура внутри Map, которая хранит все пары ключ-значение.
Возвращает множество Set пар, которое можно перебрать Итератором.

public static interface Map.Entry<K,V>

Объекты Map.Entry действительны только во время итерации.

Map<String, String> map = new HashMap<>();

Set set = map.entrySet();                  // множество сопосталений ключ-значение

Iterator iterator = set.iterator();        // итерация по элементам множества

while(iterator.hasNext()) {
    Map.Entry entry = (Map.Entry)iterator.next();
    System.out.print(entry.getKey() + ": ");
    System.out.println(entry.getValue());
}

Класс HashMap

HashMap использует хэш-таблицу для реализации интерфейса Map.
Хранит значения в произвольном порядке.
Структура хранения данных: bucket (корзина).

Позволяет задавать ключ или значение ключевым словом null
Объекты с null ключами всегда записываются в нулевую ячейку массива.

public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable

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

Хеш-функция получает ключ, а на выходе возвращает целое число – хеш-код.
По полученному хэш-коду ключ помещается по определенному индексу хеш-таблицы.
За хеш-функцию отвечает метод hashCode()

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

В качестве ключей рекомендуется использовать только неизменяемые immutable типы, например класс String или Integer.

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

Класс LinkedHashMap

LinkedHashMap поддерживает связанный список записей на Map в том порядке, в котором они были вставлены.

public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>

Класс TreeMap

Структура хранения данных красно-черное сбалансированное дерево.
По-умолчанию TreeMap сортируется по ключам с использованием принципа натуральной сортировки, но это поведение может быть настроено под конкретную задачу при помощи объекта класса Comparator.

Гарантирует, что все элементы будут отсортированы в порядке возрастания ключа.

public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, Serializable

Красно-черное дерево – это самобалансирующаяся версия двоичного поиска.
Обеспечивает логарифмическую сложность О(log(n)) операций добавления, удаления и поиска.

Верхний элемент – это корень дерева (чёрный цвет).
Все остальные элементы распределяются налево или направо в зависимости от значения хешей:

  • Все левые потомки должны быть меньше корневого узла (или равны ему)

  • Все правые потомки должны быть больше

Интерфейс Set

Set – это множество однотипных элементов.
Добавляет ограничение, которое запрещает повторяющиеся элементы.
Эквивалентность элементов проверяется с помощью метода equals()

public interface Set<E> extends Collection<E>

У Set нет индексов – нельзя получить элемент или записать значение в коллекцию по определенному индексу.

Можно делать три типа операций: добавлять, удалять элементы и проверять, есть ли во множестве этот элемент. Методов get() и set() нет.

Основные методы Set:

add(E e)

Добавляет указанный элемент в это множество, если он отсутствует.

addAll(Collection<? extends E> c)

Добавляет все элементы из указанной коллекции в это множество, если они еще не присутствуют.

clear()

Удаляет все элементы из этого множества.

contains(Object o)

Возвращает true, если это множество содержит указанный элемент.

containsAll(Collection<?> c)

Возвращает true, если это множество содержит все элементы указанной коллекции.

remove(Object o)

Удаляет указанный элемент из этого множества, если он присутствует.

removeAll(Collection<?> c)

Удаляет из этого множества все элементы, содержащиеся в указанной коллекции.

retainAll(Collection<?> c)

Сохраняет только те элементы в этом множестве, которые содержатся в указанной коллекции.
Остальные элементы удаляются.

size()

Возвращает количество элементов в этом множестве.

Класс HashSet

Класс HashSet расширяет AbstractSet и реализует интерфейс Set.

Не имеет собственной реализации – под капотом использует HashMap.
В качестве значения value HashMap используется заглушка.
Значения HashSet – это ключи внутренней HashMap.

public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, Serializable

Основные особенности HashSet:

  • Операции add(), remove(), contains() и size() за константное время O(1)

  • Порядок элементов может меняться

Класс LinkedHashSet

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

public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, Serializable

Класс TreeSet

TreeSet использует под капотом TreeMap.
Использует дерево для хранения.
Объекты хранятся в отсортированном и возрастающем порядке.

public class TreeSet<E>
extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, Serializable

Основные особенности TreeSet:

  • Временная сложность базовых операций add(), remove(), contains()
    O(log(n))

  • Гарантирует порядок элементов

Класс Collections

Класс Collections содержит различные статические методы для работы с Коллекциями.

Основные методы Collections:

addAll(Collection<? super T> c, T... elements)

Добавляет все указанные элементы в указанную коллекцию.

binarySearch(List<? extends Comparable<? super T>> list, T key)

Поиск указанного объекта в указанном List с помощью алгоритма двоичного поиска.

copy(List<? super T> dest, List<? extends T> src)

Копирует все элементы из одного List в другой.

disjoint(Collection<?> c1, Collection<?> c2)

Возвращает true, если две указанные коллекции не имеют общих элементов.

emptyList()

Возвращает пустой List.

emptyMap()

Возвращает пустую Map.

emptySet()

Возвращает пустой Set.

fill(List<? super T> list, T obj)

Заменяет все элементы указанного списка указанным элементом.

frequency(Collection<?> c, Object o)

Возвращает количество элементов в указанной коллекции, которые равны указанному объекту.

max(Collection<? extends T> coll)

Возвращает максимальный элемент данной коллекции в соответствии с естественным упорядочением ее элементов.

min(Collection<? extends T> coll)

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

nCopies(int n, T o)

Возвращает неизменяемый List, состоящий из n копий указанного объекта.

newSetFromMap(Map<E,Boolean> map)

Возвращает Set из указанной Map.

replaceAll(List<T> list, T oldVal, T newVal)

Заменяет все указанные значения в List другим значением.

reverse(List<?> list)

Изменяет порядок элементов в указанном List в обратном порядке.

reverseOrder()

Возвращает Компаратор, который накладывает обратный естественный порядок на коллекцию объектов, реализующих сопоставимый интерфейс.

rotate(List<?> list, int distance)

Изменяет очередь элементов в указанном List на указанном промежутке.

shuffle(List<?> list)

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

singleton(T o)

Возвращает неизменяемый Set, содержащий только один указанный объект.

singletonList(T o)

Возвращает неизменяемый List, содержащий только один указанный объект.

singletonMap(K key, V value)

Возвращает неизменяемую Map, содержащую только одно сопоставление ключ - значение.

sort(List<T> list)

Сортирует указанный List по возрастанию в соответствии с естественным порядком его элементов.

swap(List<?> list, int i, int j)

Замена местами двух элементов в указанных позициях в указанном List.

synchronizedCollection(Collection<T> c)

Возвращает синхронизированную коллекцию, поддерживаемую указанной коллекцией.

unmodifiableCollection(Collection<? extends T> c)

Возвращает неизменяемую указанную коллекцию.

Потокобезопасные Concurrent коллекции

Concurrent Collections – это набор коллекций, более эффективно работающих в многопоточной среде чем стандартные коллекции из пакета java.util

Вместо базового враппера (обертки) Collections.synchronizedCollection с блокированием доступа ко всей коллекции используются блокировки по сегментам данных или же оптимизируется работа для параллельного чтения данных по wait-free алгоритмам.

Классы Hashable и Collections.synchronizedMap синхронизированы.
Пакет java.util.concurrent предоставляет реализации одновременных коллекций:

  • ConcurrentHashMap – вместо HashMap

  • ConcurrentSkipListMap – вместо TreeMap

  • ConcurrentSkipListSet

  • CopyOnWriteArrayList – вместо ArrayList

  • CopyOnWriteArraySet

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

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

Сортировка коллекций

Чтобы сортировать объекты в коллекции – нужно прописать правила их сравнения.
Для этого в классе, чьи объекты будут сортированы, должен быть интерфейс Comparable или Comparator.

  • Comparable – естественная сортировка класса

  • Comparator – используется, когда в классе не реализован Comparable, либо реализован, но с неподходящей нам логикой сравнения объектов.

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

  1. Рефлексивность – сравнение элемента с самим собой всегда возвращает 0

  2. Антисимметричность – сравнение A с B, и B с A должны дать разный знак.

  3. Транзитивность – если сравнение A с B, и B с C выдает одинаковый знак, то и сравнение A с C должно выдать такой же знак.

Интерфейс Comparable

Интерфейс Comparable накладывает полный порядок на объекты каждого класса, который его реализует.
Этот порядок называется естественный порядок класса, а метод compareTo() класса называется его естественным методом сравнения.

public interface Comparable<T>

Списки и массивы объектов, реализующих этот интерфейс, могут быть автоматически отсортированы методами Collections.sort() и Arrays.sort().

Объекты, реализующие этот интерфейс, могут использоваться в качестве ключей в SortedMap или как элементы в SortedSet без необходимости указания Компаратора.

Чтобы задать свой способ сравнения объектов, нужно переопределить метод compareTo(), где прописать логику сравнения.

class Person implements Comparable<Person> {
	private String fullName;
	private Integer uuid;  
  
    @Override
    public int compareTo(Person person) {
        return uuid - person.uuid;
    }
}

Метод compareTo(Object o) сравнивает этот объект с указанным объектом. Возвращает отрицательное целое число, ноль или положительное целое число, если этот объект меньше, равен или больше указанного объекта.
В предопределенном методе вместо выражения можно использовать его:

    @Override
    public int compareTo(Person person) {     
        return uuid.compareTo(person.uuid);
    }

Рекомендуется чтобы (x.compareTo(y)==0) был равен (x.equals(y))

Можно сравнивать по нескольким полям класса.
Например, если fullName совпадают – тогда сравнить по uuid

    @Override
    public int compareTo(Person person) {
        int fullNameComparator = fullName.compareTo(person.fullName);
        int uuidComparator = uuid.compareTo(person.uuid);
      
        return (fullNameComparator != 0 ? fullNameComparator : uuidComparator);
    }

Интерфейс Comparator

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

@FunctionalInterface
public interface Comparator<T>

Можно создать отдельный класс компаратор, реализовав в нем Comparator.
Такой класс будет содержать нужную логику сравнения и его можно будет использовать в аргументах методов, которые требуют Компаратор.

public class PersonUuidComparator implements Comparator<Person> {

    @Override
    public int compare(Person p1, Person p2) {
        return p1.uuid - p2.uuid;
    }
}

Можно создать несколько Компараторов для класса и использовать нужный.

public class PersonNameComparator implements Comparator<Person> {

    @Override
    public int compare(Person p1, Person p2) {
        return p1.name.compareTo(p2.uuid);
    }
}

Можно применять сразу несколько компараторов по принципу приоритета thenComparing()

Comparator<Person> personComparator = new PersonNameComparator()
        .thenComparing(new PersonUuidComparator());

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

Comparator<Person> personUuidComparator = Comparator.comparing(Person::getUuid);
List<Person> people = new ArrayList<Person>();

people.sort((person1, person2) -> person1.uuid - person2.uuid());

people.sort((person1, person2) -> person1.uuid.compareTo(person2.uuid()));

Для сортировки в обратном порядке reversed()

Comparator<Person> uuidComparator = 
  (person1, person2) -> person1.getUuid().compareTo(person2.getUuid());

people.sort(uuidComparator.reversed());

Список ссылок на источники информации

Комментарии (0)