89. Чем отличается ArrayList от LinkedList?
Это один из самых популярных вопросов наравне с вопросом о внутреннем устройстве HashMap. Ни одно собеседование не обходится без него, и поэтому ответ на него у вас должен “отскакивать от зубов”. Помимо очевидного — разного названия — они отличаются внутренним устройством. Ранее мы разбирали внутренние устройство и ArrayList-а и LinkedList-а, поэтому вдаваться в детали их реализации я не буду. Лишь напомню, что ArrayList реализован на основе внутреннего массива, который по надобности увеличивается по формуле:
<размерТекущегоМассива> * 3 / 2 + 1
В то же время LinkedList реализован на основе внутреннего двухсвязного списка, то есть, каждый элемент имеет ссылку на предыдущий и следующий, исключая значения, которые являются началом/концом списка.
Этот вопрос любят задавать в формате: “Что лучше — ArrayList или LinkedList?”, надеясь вас подловить. Ведь если вы в качестве ответа укажете на один из них, это будет неправильный ответ.
Вместо этого вам стоит уточнить, о какой конкретной ситуации идет речь — доступ по индексу или вставка в середину списка. В зависимости от ответа вы сможете объяснить свой выбор.
Ранее я уже описывал, как работает ArrayList и LinkedList в той или иной ситуации. Давайте подытожим это, поставив их в один ряд для сравнения:
Добавление элемента (add)Добавление нового элемента без указания индекса как местоположения будет происходить автоматически в конец обоих списков. В LinkedList новый элемент станет новым хвостом (происходит только перезаписывание пары ссылок — алгоритмическая сложность O(1)).
В ArrayList будет добавлен новый элемент в последнюю пустую ячейку массива — O(1).
Добавление элемента по индексу как правило подразумевает вставку примерно в середину списка. В LinkedList сперва будет вестись поиск нужного места с помощью перебора элементов с “хвоста” и “головы” — O(n/2), а после — вставка значения путем переопределения ссылок элементов, между которыми вставляется новый — O(1). Суммарная алгоритмическая сложность данного действия будет O(n/2).
ArrayList в данной ситуации по индексу находит элемент — O(1), и все элементы справа (включая элемент, который уже хранится по данному индексу) двигаются на одну единицу вправо (при этом возможно понадобится создание нового списка и копирование элементов в него) — O(n/2). Суммарная сложность — O(n/2).Добавление элемента в начало списка в LinkedList будет ситуация схожая с добавлением в конец: новый элемент станет новой “головой” — O(1), в то же время когда ArrayList-у нужно будет двигать все элементы вправо — O(n).
90. Чем отличается ArrayList от HashSet?
Если ArrayList и LinkedList можно было сравнить по операциям — где кто лучше — то с ArrayList с HashSet сравнить уже не так просто, ведь это совершенно разные коллекции. Можно сравнить одно сладкое блюдо с другим, но с мясным уже получится — больно уж они разные. Тем не менее, я попробую привести их некоторые различия:ArrayList реализует интерфейс List, в то время как HashSet реализует интерфейс Set;
В ArrayList возможен доступ по индексу элемента: операция get имеет алгоритмическую сложность O(1), а в HashSet необходимый элемент можно получить лишь путём перебора, а это у нас от O(1) до O(n);
ArrayList допускает присутствие дубликатов элементов. В HashSet все элементы уникальны: добавить в HashSet элемент, аналог которого уже присутствует в коллекции, не получится (проверка дубликатов ведется по hashcode, отсюда и название этой коллекции);
ArrayList реализован с помощью внутреннего массива, а HashSet реализован с помощью внутренней HashMap;
ArrayList поддерживает порядок вставки элементов, в то время как HashSet — это неупорядоченное множество и не поддерживает порядок элементов;
ArrayList допускает любое количество пустых значений (null), в HashSet можно вставить лишь одно значение null (как-никак, уникальность элементов).
91. Зачем в Java такое разнообразие имплементации динамического массива?
Ну, это скорее философский вопрос. Ну а зачем придумывают такое количество новых разнообразных технологий? Для удобства. Собственно, так же и с большим количеством имплементаций динамического массива. Ни одну из них нельзя назвать лучшей или идеальной. У каждой есть преимущество в какой-то конкретной ситуации. И наша задача — знать их различия, их сильные/слабые стороны: чтобы суметь в нужной ситуации использовать самую подходящую из них.92. Зачем в Java такое разнообразие имплементаций key-value storage?
Здесь ситуация такая же, как и с имплементациями динамического массива. Однозначно лучших нет: у каждой есть сильные и слабые стороны. И мы, конечно, должны по максимуму использовать сильные стороны. Пример: в пакете concurrent, в котором есть множество многопоточных технологий, имеются свои Concurrent коллекции. У той же ConcurrentHashMap есть преимущество в безопасности многопоточной работы с данными в сравнении с обычной HashMap, но не в многопоточной среде она проигрывает в скорости работы. Ну а имплементации, которые ни в одной из ситуаций не бывают сильнейшими, постепенно перестают использовать. Пример: Hashtable, которая изначально задумывалась как потокобезопасная HashMap, но ConcurrentHashMap превзошла ее при работе в многопоточной среде, и в итоге о Hashtable позабыли и перестали использовать.93. Как отсортировать коллекцию элементов?
Первое, что нужно сказать, — класс элемента коллекции должен имплементировать интерфейс Comparable и его метод compareTo. Или же нужен класс, который имплементирует Comaprator с его методом comparator. Подробнее о них можно почитать в этом посте. Оба способа указывают, каким образом нужно сравнивать объекты данного типа. При сортировке это критически важно, ведь нужно понимать принцип, по которому элементы можно сравнить. В основном используется способ через имплементацию Comparable, реализуемый непосредственно в классе, который вы хотите сортировать. В то же время применение Comparator-а более редко. Скажем, вы используете класс с какой-то библиотеки, у которого нет реализации Comparable, но вам как-то нужно будет его сортировать. Не имея возможности изменить код данного класса (кроме как расширить его), вы можете написать реализацию Comparator-а, в котором укажете, по какому принципу нужно сравнивать объекты данного класса. И еще один пример. Допустим, вам нужны разные принципы сортировки объектов одного и того же типа, поэтому вы пишете несколько Comparator-ов которые используете в разных ситуациях. Как правило, многие классы из коробки уже реализуют интерфейс Comparable — тот же String. Собственно, при их использовании вам не нужно париться, как их сравнить. Вы просто берете и используете их. Первый и самый очевидный способ — использовать коллекцию типа TreeSet или TreeMap, которые хранят элементы в ужеотсортированном порядке, согласно компаратору класса элементов. Не забывайте, что TreeMap сортирует ключи, но не значения. Если вы используете имплементацию Comparator вместо Comparable, вам нужно будет передать его объект в конструктор коллекции при создании:
TreeSet treeSet = new TreeSet(customComparator);
А что если у вас коллекция другого типа? Как её отсортировать?
В этом случае подходит второй способ утилитного класса Collections — метод sort(). Он статический, поэтому всё, что вам нужно — имя класса и метод, в который передается необходимый список.
Например:
Collections.sort(someList);
Если вы используете не Comparable, а реализацию Comparator, его нужно передать вторым параметром:
Collections.sort(someList, customComparator);
В итоге внутренний порядок элементов переданного списка изменится: он будет отсортирован согласно компаратору элементов.
Отмечу, что передаваемый список элементов должен быть мутабельным, т.е. изменяемым, иначе метод не сработает и будет выброшено UnsupportedOperationException.
В качестве третьего способа можно использовать Stream операцию sort, которая сортирует элементы коллекции, если используется имплементация Comparable:
someList = someList.stream().sorted().collect(Collectors.toList());
если Comparator:
someList = someList.stream().sorted(customComparator).collect(Collectors.toList());
Подробнее о Stream можно почитать в этой статье.
Четвертый способ — ручная реализация сортировки, например, сортировки пузырьком или сортировки слиянием.
Class Object. Equals and HashCode
94. Дайте краткую характеристику class object в Java
Во второй части разбора мы уже говорили о методах класса Object, и я напомню, что класс Object — прародитель всех классов в Java. У него есть 11 методов, которые, соответственно, наследуются всеми классами. Информацию обо всех 11 методах можно найти во второй части разбора вопросов.95. Для чего используют Equals и HashCode в Java?
hashCode() — это метод класса Object, который наследуется всеми классами. Его задача — генерирование некоторого числа, которое представляет конкретный объект. Примером использования данного метода может служить его применение в HashMap на объекте ключа для дальнейшего определения локального хешкода, по которому определится ячейка внутреннего массива (бакета), в которой будет сохранена пара. Подробно о работе HashMap мы говорили в 9 части разбора, поэтому особо останавливаться на этом не будем. Также как правило данный метод используется в методе equals() как один из его основных инструментов определения идентичности объектов. equals() — метод класса Object, задача которого — сравнивать объекты и определять, равны они или нет. Данный метод используется повсеместно там, где нам необходимо сравнить объекты, ведь обычное сравнение через == не подходит для объектов, т.к. сравнивает только ссылки на них.96. Расскажите про контракт между Equals и HashCode в Java?
Первое, что скажу — для корректной работы методов equals() и hashCode() их нужно правильно переопределить. После этого они должны соблюдать правила:- одинаковые объекты, для которых сравнение через equals возвращает true, обязательно имеют одинаковые хеш-коды;
- объекты с одинаковыми хеш-кодами не всегда могут быть равны.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ