Вопросы для собеседования
Java Collections Framework
- Что такое «коллекция»?
- Назовите основные интерфейсы JCF и их реализации.
- Расположите в виде иерархии следующие интерфейсы:
List
,Set
,Map
,SortedSet
,SortedMap
,Collection
,Iterable
,Iterator
,NavigableSet
,NavigableMap
. - Почему
Map
— это неCollection
, в то время какList
иSet
являютсяCollection
? - В чем разница между классами
java.util.Collection
иjava.util.Collections
? - Что такое «fail-fast поведение»?
- Какая разница между fail-fast и fail-safe?
- Приведите примеры итераторов, реализующих поведение fail-safe
- Чем различаются
Enumeration
иIterator
. - Как между собой связаны
Iterable
иIterator
? - Как между собой связаны
Iterable
,Iterator
и «for-each»? - Сравните
Iterator
иListIterator
. - Что произойдет при вызове
Iterator.next()
без предварительного вызоваIterator.hasNext()
? - Сколько элементов будет пропущено, если
Iterator.next()
будет вызван после 10-ти вызововIterator.hasNext()
? - Как поведёт себя коллекция, если вызвать
iterator.remove()
? - Как поведёт себя уже инстанциированный итератор для
collection
, если вызватьcollection.remove()
? - Как избежать
ConcurrentModificationException
во время перебора коллекции? - Какая коллекция реализует дисциплину обслуживания FIFO?
- Какая коллекция реализует дисциплину обслуживания FILO?
- Чем отличается
ArrayList
отVector
? - Зачем добавили
ArrayList
, если уже былVector
? - Чем отличается
ArrayList
отLinkedList
? В каких случаях лучше использовать первый, а в каких второй? - Что работает быстрее
ArrayList
илиLinkedList
? - Какое худшее время работы метода
contains()
для элемента, который есть вLinkedList
? - Какое худшее время работы метода
contains()
для элемента, который есть вArrayList
? - Какое худшее время работы метода
add()
дляLinkedList
? - Какое худшее время работы метода
add()
дляArrayList
? - Необходимо добавить 1 млн. элементов, какую структуру вы используете?
- Как происходит удаление элементов из
ArrayList
? Как меняется в этом случае размерArrayList
? - Предложите эффективный алгоритм удаления нескольких рядом стоящих элементов из середины списка, реализуемого
ArrayList
. - Сколько необходимо дополнительной памяти при вызове
ArrayList.add()
? - Сколько выделяется дополнительно памяти при вызове
LinkedList.add()
? - Оцените количество памяти на хранение одного примитива типа
byte
вLinkedList
? - Оцените количество памяти на хранение одного примитива типа
byte
вArrayList
? - Для
ArrayList
или дляLinkedList
операция добавления элемента в середину (list.add(list.size()/2, newElement)
) медленнее? - В реализации класса
ArrayList
есть следующие поля:Object[] elementData
,int size
. Объясните, зачем хранить отдельноsize
, если всегда можно взятьelementData.length
? - Сравните интерфейсы
Queue
иDeque
. - Кто кого расширяет:
Queue
расширяетDeque
, илиDeque
расширяетQueue
? - Почему
LinkedList
реализует иList
, иDeque
? LinkedList
— это односвязный, двусвязный или четырехсвязный список?- Как перебрать элементы
LinkedList
в обратном порядке, не используя медленныйget(index)
? - Что позволяет сделать
PriorityQueue
? Stack
считается «устаревшим». Чем его рекомендуют заменять? Почему?- Зачем нужен
HashMap
, если естьHashtable
? - В чем разница между
HashMap
иIdentityHashMap
? Для чего нужнаIdentityHashMap
? - В чем разница между
HashMap
иWeakHashMap
? Для чего используетсяWeakHashMap
? - В
WeakHashMap
используются WeakReferences. А почему бы не создатьSoftHashMap
на SoftReferences? - В
WeakHashMap
используются WeakReferences. А почему бы не создатьPhantomHashMap
на PhantomReferences? LinkedHashMap
— что в нем отLinkedList
, а что отHashMap
?- В чем проявляется «сортированность»
SortedMap
, кроме того, чтоtoString()
выводит все элементы по порядку? - Как устроен
HashMap
? - Согласно Кнуту и Кормену существует две основных реализации хэш-таблицы: на основе открытой адресации и на основе метода цепочек. Как реализована
HashMap
? Почему, по вашему мнению, была выбрана именно эта реализация? В чем плюсы и минусы каждого подхода? - Как работает
HashMap
при попытке сохранить в него два элемента по ключам с одинаковымhashCode()
, но для которыхequals() == false
? - Какое начальное количество корзин в
HashMap
? - Какова оценка временной сложности операций над элементами из
HashMap
? Гарантирует лиHashMap
указанную сложность выборки элемента? - Возможна ли ситуация, когда
HashMap
выродится в список даже с ключами имеющими разныеhashCode()
? - В каком случае может быть потерян элемент в
HashMap
? - Почему нельзя использовать
byte[]
в качестве ключа вHashMap
? - Какова роль
equals()
иhashCode()
вHashMap
? - Каково максимальное число значений
hashCode()
? - Какое худшее время работы метода get(key) для ключа, которого нет в
HashMap
? - Какое худшее время работы метода get(key) для ключа, который есть в
HashMap
? - Сколько переходов происходит в момент вызова
HashMap.get(key)
по ключу, который есть в таблице? - Сколько создается новых объектов, когда вы добавляете новый элемент в
HashMap
? - Как и когда происходит увеличение количества корзин в
HashMap
? - Объясните смысл параметров в конструкторе
HashMap(int initialCapacity, float loadFactor)
. - Будет ли работать
HashMap
, если все добавляемые ключи будут иметь одинаковыйhashCode()
? - Как перебрать все ключи
Map
? - Как перебрать все значения
Map
? - Как перебрать все пары «ключ-значение» в
Map
? - В чем отличия
TreeSet
иHashSet
? - Что будет, если добавлять элементы в
TreeSet
по возрастанию? - Чем
LinkedHashSet
отличается отHashSet
? - Для
Enum
есть специальный классjava.util.EnumSet
. Зачем? Чем авторов не устраивалHashSet
илиTreeSet
? - Какие существуют способы перебирать элементы списка?
- Каким образом можно получить синхронизированные объекты стандартных коллекций?
- Как получить коллекцию только для чтения?
- Напишите однопоточную программу, которая заставляет коллекцию выбросить
ConcurrentModificationException
. - Приведите пример, когда какая-либо коллекция выбрасывает
UnsupportedOperationException
. - Реализуйте симметрическую разность двух коллекций используя методы
Collection
(addAll(...)
,removeAll(...)
,retainAll(...)
). - Как, используя LinkedHashMap, сделать кэш c «invalidation policy»?
- Как одной строчкой скопировать элементы любой
collection
в массив? - Как одним вызовом из
List
получитьList
со всеми элементами, кроме первых и последних 3-х? - Как одной строчкой преобразовать
HashSet
вArrayList
? - Как одной строчкой преобразовать
ArrayList
вHashSet
? - Сделайте
HashSet
из ключейHashMap
. - Сделайте
HashMap
изHashSet<Map.Entry<K, V>>
.
Что такое «коллекция»?
«Коллекция» — это структура данных, набор каких-либо объектов. Данными (объектами в наборе) могут быть числа, строки, объекты пользовательских классов и т.п.
к оглавлению
Назовите основные интерфейсы JCF и их реализации.
На вершине иерархии в Java Collection Framework располагаются 2 интерфейса: Collection
и Map
. Эти интерфейсы разделяют все коллекции, входящие во фреймворк на две части по типу хранения данных: простые последовательные наборы элементов и наборы пар «ключ — значение» соответственно.
Интерфейс Collection
расширяют интерфейсы:
List
(список) представляет собой коллекцию, в которой допустимы дублирующие значения. Реализации:ArrayList
— инкапсулирует в себе обычный массив, длина которого автоматически увеличивается при добавлении новых элементов. Элементы такой коллекции пронумерованы, начиная от нуля, к ним можно обратиться по индексу.LinkedList
(двунаправленный связный список) — состоит из узлов, каждый из которых содержит как собственно данные, так и две ссылки на следующий и предыдущий узел.Vector
— реализация динамического массива объектов, методы которой синхронизированы.Stack
— реализация стека LIFO (last-in-first-out).
Set
(сет) описывает неупорядоченную коллекцию, не содержащую повторяющихся элементов. Реализации:HashSet
— использует HashMap для хранения данных. В качестве ключа используется добавляемый элемент, в качестве значения — заглушка Object. Из-за особенностей реализации порядок элементов не гарантируется при добавлении.LinkedHashSet
— гарантирует, что порядок элементов при обходе коллекции будет идентичен порядку добавления элементов.TreeSet
— предоставляет возможность управлять порядком элементов в коллекции при помощи объектаComparator
, либо сохраняет элементы с использованием «natural ordering».
Queue
(очередь) предназначена для хранения элементов с предопределённым способом вставки и извлечения FIFO (first-in-first-out):PriorityQueue
— предоставляет возможность управлять порядком элементов в коллекции при помощи объектаComparator
, либо сохраняет элементы с использованием «natural ordering».ArrayDeque
— реализация интерфейсаDeque
, который расширяет интерфейсQueue
методами, позволяющими реализовать конструкцию вида LIFO (last-in-first-out).
Интерфейс Map
реализован классами:
Hashtable
— хэш-таблица, методы которой синхронизированы. Не позволяет использоватьnull
в качестве значения или ключа и не является упорядоченной.HashMap
— хэш-таблица. Позволяет использоватьnull
в качестве значения или ключа и не является упорядоченной.LinkedHashMap
— упорядоченная реализация хэш-таблицы.TreeMap
— реализация, основанная на красно-чёрных деревьях. Является упорядоченной и предоставляет возможность управлять порядком элементов в коллекции при помощи объектаComparator
, либо сохраняет элементы с использованием «natural ordering».WeakHashMap
— реализация хэш-таблицы, которая организована с использованием weak references для ключей (сборщик мусора автоматически удалит элемент из коллекции при следующей сборке мусора, если на ключ этого элемента нет жёстких ссылок).
к оглавлению
Расположите в виде иерархии следующие интерфейсы: List
, Set
, Map
, SortedSet
, SortedMap
, Collection
, Iterable
, Iterator
, NavigableSet
, NavigableMap
.
Iterable
Collection
List
Set
SortedSet
NavigableSet
Map
SortedMap
NavigableMap
Iterator
к оглавлению
Почему Map
— это не Collection
, в то время как List
и Set
являются Collection
?
Collection
представляет собой совокупность некоторых элементов. Map
— это совокупность пар «ключ-значение».
к оглавлению
В чем разница между классами java.util.Collection
и java.util.Collections
?
java.util.Collections
— набор статических методов для работы с коллекциями.
java.util.Collection
— один из основных интерфейсов Java Collections Framework.
к оглавлению
Что такое «fail-fast поведение»?
fail-fast поведение означает, что при возникновении ошибки или состояния, которое может привести к ошибке, система немедленно прекращает дальнейшую работу и уведомляет об этом. Использование fail-fast подхода позволяет избежать недетерминированного поведения программы в течение времени.
В Java Collections API некоторые итераторы ведут себя как fail-fast и выбрасывают ConcurrentModificationException
, если после его создания была произведена модификация коллекции, т.е. добавлен или удален элемент напрямую из коллекции, а не используя методы итератора.
Реализация такого поведения осуществляется за счет подсчета количества модификаций коллекции (modification count):
- при изменении коллекции счетчик модификаций так же изменяется;
- при создании итератора ему передается текущее значение счетчика;
- при каждом обращении к итератору сохраненное значение счетчика сравнивается с текущим, и, если они не совпадают, возникает исключение.
к оглавлению
Какая разница между fail-fast и fail-safe?
В противоположность fail-fast, итераторы fail-safe не вызывают никаких исключений при изменении структуры, потому что они работают с клоном коллекции вместо оригинала.
к оглавлению
Приведите примеры итераторов, реализующих поведение fail-safe
Итератор коллекции CopyOnWriteArrayList
и итератор представления keySet
коллекции ConcurrentHashMap
являются примерами итераторов fail-safe.
к оглавлению
Чем различаются Enumeration
и Iterator
.
Хотя оба интерфейса и предназначены для обхода коллекций между ними имеются существенные различия:
- с помощью
Enumeration
нельзя добавлять/удалять элементы; - в
Iterator
исправлены имена методов для повышения читаемости кода (Enumeration.hasMoreElements()
соответствуетIterator.hasNext()
,Enumeration.nextElement()
соответствуетIterator.next()
и т.д); Enumeration
присутствуют в устаревших классах, таких какVector
/Stack
, тогда какIterator
есть во всех современных классах-коллекциях.
к оглавлению
Как между собой связаны Iterable
и Iterator
?
Интерфейс Iterable
имеет только один метод — iterator()
, который возвращает Iterator
.
к оглавлению
Как между собой связаны Iterable
, Iterator
и «for-each»?
Классы, реализующие интерфейс Iterable
, могут применяться в конструкции for-each
, которая использует Iterator
.
к оглавлению
Сравните Iterator
и ListIterator
.
ListIterator
расширяет интерфейсIterator
ListIterator
может быть использован только для перебора элементов коллекцииList
;Iterator
позволяет перебирать элементы только в одном направлении, при помощи методаnext()
. Тогда какListIterator
позволяет перебирать список в обоих направлениях, при помощи методовnext()
иprevious()
;ListIterator
не указывает на конкретный элемент: его текущая позиция располагается между элементами, которые возвращают методыprevious()
иnext()
.- При помощи
ListIterator
вы можете модифицировать список, добавляя/удаляя элементы с помощью методовadd()
иremove()
.Iterator
не поддерживает данного функционала.
к оглавлению
Что произойдет при вызове Iterator.next()
без предварительного вызова Iterator.hasNext()
?
Если итератор указывает на последний элемент коллекции, то возникнет исключение NoSuchElementException
, иначе будет возвращен следующий элемент.
к оглавлению
Сколько элементов будет пропущено, если Iterator.next()
будет вызван после 10-ти вызовов Iterator.hasNext()
?
Нисколько — hasNext()
осуществляет только проверку наличия следующего элемента.
к оглавлению
Как поведёт себя коллекция, если вызвать iterator.remove()
?
Если вызову iterator.remove()
предшествовал вызов iterator.next()
, то iterator.remove()
удалит элемент коллекции, на который указывает итератор, в противном случае будет выброшено IllegalStateException()
.
к оглавлению
Как поведёт себя уже инстанциированный итератор для collection
, если вызвать collection.remove()
?
При следующем вызове методов итератора будет выброшено ConcurrentModificationException
.
к оглавлению
Как избежать ConcurrentModificationException
во время перебора коллекции?
- Попробовать подобрать или реализовать самостоятельно другой итератор, работающий по принципу fail-safe.
- Использовать
ConcurrentHashMap
иCopyOnWriteArrayList
. - Преобразовать список в массив и перебирать массив.
- Блокировать изменения списка на время перебора с помощью блока
synchronized
.
Отрицательная сторона последних двух вариантов — ухудшение производительности.
к оглавлению
Какая коллекция реализует дисциплину обслуживания FIFO?
FIFO, First-In-First-Out («первым пришел-первым ушел») — по этому принципу построена коллекция Queue
.
к оглавлению
Какая коллекция реализует дисциплину обслуживания FILO?
FILO, First-In-Last-Out («первым пришел, последним ушел») — по этому принципу построена коллекция Stack
.
к оглавлению
Чем отличается ArrayList
от Vector
?
Зачем добавили ArrayList
, если уже был Vector
?
- Методы класса
Vector
синхронизированы, аArrayList
— нет; - По умолчанию,
Vector
удваивает свой размер, когда заканчивается выделенная под элементы память.ArrayList
же увеличивает свой размер только на половину.
Vector
это устаревший класс и его использование не рекомендовано.
к оглавлению
Чем отличается ArrayList
от LinkedList
? В каких случаях лучше использовать первый, а в каких второй?
ArrayList
это список, реализованный на основе массива, а LinkedList
— это классический двусвязный список, основанный на объектах с ссылками между ними.
ArrayList
:
- доступ к произвольному элементу по индексу за константное время O(1);
- доступ к элементам по значению за линейное время O(N);
- вставка в конец в среднем производится за константное время O(1);
- удаление произвольного элемента из списка занимает значительное время т.к. при этом все элементы, находящиеся «правее» смещаются на одну ячейку влево (реальный размер массива (capacity) не изменяется);
- вставка элемента в произвольное место списка занимает значительное время т.к. при этом все элементы, находящиеся «правее» смещаются на одну ячейку вправо;
- минимум накладных расходов при хранении.
LinkedList
:
- на получение элемента по индексу или значению потребуется линейное время O(N);
- но доступ к первому и последнему элементу списка всегда осуществляется за константное время O(1) — ссылки постоянно хранятся на первый и последний элемент;
- на добавление и удаление в начало или конец списка потребуется константное O(1);
- вставка или удаление в/из произвольного место константное O(1);
- но поиск позиции вставки и удаления за линейное время O(N);
- требует больше памяти для хранения такого же количества элементов, потому что кроме самого элемента хранятся еще указатели на следующий и предыдущий элементы списка.
В целом, LinkedList
в абсолютных величинах проигрывает ArrayList
и по потребляемой памяти, и по скорости выполнения операций. LinkedList
предпочтительно применять, когда нужны частые операции вставки/удаления или в случаях, когда необходимо гарантированное время добавления элемента в список.
к оглавлению
Что работает быстрее ArrayList
или LinkedList
?
Смотря какие действия будут выполняться над структурой.
см. Чем отличается ArrayList
от LinkedList
к оглавлению
Какое худшее время работы метода contains()
для элемента, который есть в LinkedList
?
O(N). Время поиска элемента линейно пропорционально количеству элементов в списке.
к оглавлению
Какое худшее время работы метода contains()
для элемента, который есть в ArrayList
?
O(N). Время поиска элемента линейно пропорционально количеству элементов с списке.
к оглавлению
Какое худшее время работы метода add()
для LinkedList
?
O(N). Добавление в начало/конец списка осуществляется за время O(1).
к оглавлению
Какое худшее время работы метода add()
для ArrayList
?
O(N). Вставка элемента в конец списка осуществляется за время O(1), но если вместимость массива недостаточна, то происходит создание нового массива с увеличенным размером и копирование всех элементов из старого массива в новый.
к оглавлению
Необходимо добавить 1 млн. элементов, какую структуру вы используете?
Однозначный ответ можно дать только исходя из информации о том в какую часть списка происходит добавление элементов, что потом будет происходить с элементами списка, существуют ли какие-то ограничения по памяти или скорости выполнения.
см. Чем отличается ArrayList
от LinkedList
к оглавлению
Как происходит удаление элементов из ArrayList
? Как меняется в этом случае размер ArrayList
?
При удалении произвольного элемента из списка, все элементы, находящиеся «правее» смещаются на одну ячейку влево и реальный размер массива (его емкость, capacity) не изменяется никак. Механизм автоматического «расширения» массива существует, а вот автоматического «сжатия» нет, можно только явно выполнить «сжатие» командой trimToSize()
.
к оглавлению
Предложите эффективный алгоритм удаления нескольких рядом стоящих элементов из середины списка, реализуемого ArrayList
.
Допустим нужно удалить n
элементов с позиции m
в списке. Вместо выполнения удаления одного элемента n
раз (каждый раз смещая на 1 позицию элементы, стоящие «правее» в списке), нужно выполнить смещение всех элементов, стоящих «правее» n + m
позиции на n
элементов «левее» к началу списка. Таким образом, вместо выполнения n
итераций перемещения элементов списка, все выполняется за 1 проход. Но если говорить об общей эффективности — то самый быстрый способ будет с использованием System.arraycopy()
, и получить к нему доступ можно через метод — subList(int fromIndex, int toIndex)
Пример:
import java.io.*; import java.util.ArrayList; public class Main { //позиция, с которой удаляем private static int m = 0; //количество удаляемых элементов private static int n = 0; //количество элементов в списке private static final int size = 1000000; //основной список (для удаления вызовом remove() и его копия для удаления путём перезаписи) private static ArrayList<Integer> initList, copyList; public static void main(String[] args){ initList = new ArrayList<>(size); for (int i = 0; i < size; i++) { initList.add(i); } System.out.println("Список из 1.000.000 элементов заполнен"); copyList = new ArrayList<>(initList); System.out.println("Создана копия спискаn"); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); try{ System.out.print("С какой позиции удаляем? > "); m = Integer.parseInt(br.readLine()); System.out.print("Сколько удаляем? > "); n = Integer.parseInt(br.readLine()); } catch(IOException e){ System.err.println(e.toString()); } System.out.println("nВыполняем удаление вызовом remove()..."); long start = System.currentTimeMillis(); for (int i = m - 1; i < m + n - 1; i++) { initList.remove(i); } long finish = System.currentTimeMillis() - start; System.out.println("Время удаления с помощью вызова remove(): " + finish); System.out.println("Размер исходного списка после удаления: " + initList.size()); System.out.println("nВыполняем удаление путем перезаписи...n"); start = System.currentTimeMillis(); removeEfficiently(); finish = System.currentTimeMillis() - start; System.out.println("Время удаления путём смещения: " + finish); System.out.println("Размер копии списка:" + copyList.size()); System.out.println("nВыполняем удаление через SubList...n"); start = System.currentTimeMillis(); initList.subList(m - 1, m + n).clear(); finish = System.currentTimeMillis() - start; System.out.println("Время удаления через саблист: " + finish); System.out.println("Размер копии списка:" + copyList.size()); } private static void removeEfficiently(){ /* если необходимо удалить все элементы, начиная с указанного, * то удаляем элементы с конца до m */ if (m + n >= size){ int i = size - 1; while (i != m - 1){ copyList.remove(i); i--; } } else{ //переменная k необходима для отсчёта сдвига начиная от места вставка m for (int i = m + n, k = 0; i < size; i++, k++) { copyList.set(m + k, copyList.get(i)); } /* удаляем ненужные элементы в конце списка * удаляется всегда последний элемент, так как время этого действия * фиксировано и не зависит от размера списка */ int i = size - 1; while (i != size - n - 1){ copyList.remove(i); i--; } //сокращаем длину списка путём удаления пустых ячеек copyList.trimToSize(); } } }
Результат выполнения:
run:
Список из 1.000.000 элементов заполнен
Создана копия списка
С какой позиции удаляем? > 600000
Сколько удаляем? > 20000
Выполняем удаление вызовом remove()...
Время удаления с помощью вызова remove(): 928
Размер исходного списка после удаления: 980000
Выполняем удаление путем перезаписи...
Время удаления путём смещения: 17
Размер копии списка:980000
Выполняем удаление через SubList...
Время удаления через саблист: 1
Размер копии списка:980000
СБОРКА УСПЕШНО ЗАВЕРШЕНА (общее время: 33 секунды)
к оглавлению
Сколько необходимо дополнительной памяти при вызове ArrayList.add()
?
Если в массиве достаточно места для размещения нового элемента, то дополнительной памяти не требуется. Иначе происходит создание нового массива размером в 1,5 раза превышающим существующий (это верно для JDK выше 1.7, в более ранних версиях размер увеличения иной).
к оглавлению
Сколько выделяется дополнительно памяти при вызове LinkedList.add()
?
Создается один новый экземпляр вложенного класса Node
.
к оглавлению
Оцените количество памяти на хранение одного примитива типа byte
в LinkedList
?
Каждый элемент LinkedList
хранит ссылку на предыдущий элемент, следующий элемент и ссылку на данные.
private static class Node<E> { E item; Node<E> next; Node<E> prev; //... }
Для 32-битных систем каждая ссылка занимает 32 бита (4 байта). Сам объект (заголовок) вложенного класса Node
занимает 8 байт. 4 + 4 + 4 + 8 = 20 байт, а т.к. размер каждого объекта в Java кратен 8, соответственно получаем 24 байта. Примитив типа byte
занимает 1 байт памяти, но в JCF примитивы упаковываются: объект типа Byte
занимает в памяти 16 байт (8 байт на заголовок объекта, 1 байт на поле типа byte
и 7 байт для кратности 8). Также напомню, что значения от -128 до 127 кэшируются и для них новые объекты каждый раз не создаются. Таким образом, в x32 JVM 24 байта тратятся на хранение одного элемента в списке и 16 байт — на хранение упакованного объекта типа Byte
. Итого 40 байт.
Для 64-битной JVM каждая ссылка занимает 64 бита (8 байт), размер заголовка каждого объекта составляет 16 байт (два машинных слова). Вычисления аналогичны: 8 + 8 + 8 + 16 = 40байт и 24 байта. Итого 64 байта.
к оглавлению
Оцените количество памяти на хранение одного примитива типа byte
в ArrayList
?
ArrayList
основан на массиве, для примитивных типов данных осуществляется автоматическая упаковка значения, поэтому 16 байт тратится на хранение упакованного объекта и 4 байта (8 для x64) — на хранение ссылки на этот объект в самой структуре данных. Таким образом, в x32 JVM 4 байта используются на хранение одного элемента и 16 байт — на хранение упакованного объекта типа Byte
. Для x64 — 8 байт и 24 байта соответственно.
к оглавлению
Для ArrayList
или для LinkedList
операция добавления элемента в середину (list.add(list.size()/2, newElement)
) медленнее?
Для ArrayList
:
- проверка массива на вместимость. Если вместимости недостаточно, то увеличение размера массива и копирование всех элементов в новый массив (O(N));
- копирование всех элементов, расположенных правее от позиции вставки, на одну позицию вправо (O(N));
- вставка элемента (O(1)).
Для LinkedList
:
- поиск позиции вставки (O(N));
- вставка элемента (O(1)).
В худшем случае вставка в середину списка эффективнее для LinkedList
. В остальных — скорее всего, для ArrayList
, поскольку копирование элементов осуществляется за счет вызова быстрого системного метода System.arraycopy()
.
к оглавлению
В реализации класса ArrayList
есть следующие поля: Object[] elementData
, int size
. Объясните, зачем хранить отдельно size
, если всегда можно взять elementData.length
?
Размер массива elementData
представляет собой вместимость (capacity) ArrayList
, которая всегда больше переменной size
— реального количества хранимых элементов. При необходимости вместимость автоматически возрастает.
к оглавлению
Сравните интерфейсы Queue
и Deque
.
Кто кого расширяет: Queue
расширяет Deque
, или Deque
расширяет Queue
?
Queue
— это очередь, которая обычно (но необязательно) строится по принципу FIFO (First-In-First-Out) — соответственно извлечение элемента осуществляется с начала очереди, вставка элемента — в конец очереди. Хотя этот принцип нарушает, к примеру, PriorityQueue
, использующая «natural ordering» или переданный Comparator
при вставке нового элемента.
Deque
(Double Ended Queue) расширяет Queue
и согласно документации, это линейная коллекция, поддерживающая вставку/извлечение элементов с обоих концов. Помимо этого, реализации интерфейса Deque
могут строится по принципу FIFO, либо LIFO.
Реализации и Deque
, и Queue
обычно не переопределяют методы equals()
и hashCode()
, вместо этого используются унаследованные методы класса Object, основанные на сравнении ссылок.
к оглавлению
Почему LinkedList
реализует и List
, и Deque
?
LinkedList
позволяет добавлять элементы в начало и конец списка за константное время, что хорошо согласуется с поведением интерфейса Deque
.
к оглавлению
LinkedList
— это односвязный, двусвязный или четырехсвязный список?
Двусвязный
: каждый элемент LinkedList
хранит ссылку на предыдущий и следующий элементы.
к оглавлению
Как перебрать элементы LinkedList
в обратном порядке, не используя медленный get(index)
?
Для этого в LinkedList
есть обратный итератор, который можно получить вызва метод descendingIterator()
.
к оглавлению
Что позволяет сделать PriorityQueue
?
Особенностью PriorityQueue
является возможность управления порядком элементов. По-умолчанию, элементы сортируются с использованием «natural ordering», но это поведение может быть переопределено при помощи объекта Comparator
, который задаётся при создании очереди. Данная коллекция не поддерживает null в качестве элементов.
Используя PriorityQueue
, можно, например, реализовать алгоритм Дейкстры для поиска кратчайшего пути от одной вершины графа к другой. Либо для хранения объектов согласно определённого свойства.
к оглавлению
Stack
считается «устаревшим». Чем его рекомендуют заменять? Почему?
Stack
был добавлен в Java 1.0 как реализация стека LIFO (last-in-first-out) и является расширением коллекции Vector
, хотя это несколько нарушает понятие стека (например, класс Vector
предоставляет возможность обращаться к любому элементу по индексу). Является частично синхронизированной коллекцией (кроме метода добавления push()
) с вытекающими отсюда последствиями в виде негативного воздействия на производительность. После добавления в Java 1.6 интерфейса Deque
, рекомендуется использовать реализации именно этого интерфейса, например, ArrayDeque
.
к оглавлению
Зачем нужен HashMap
, если есть Hashtable
?
- Методы класса
Hashtable
синхронизированы, что приводит к снижению производительности, аHashMap
— нет; HashTable
не может содержать элементыnull
, тогда какHashMap
может содержать один ключnull
и любое количество значенийnull
;- Iterator у
HashMap
, в отличие от Enumeration уHashTable
, работает по принципу «fail-fast» (выдает исключение при любой несогласованности данных).
Hashtable
это устаревший класс и его использование не рекомендовано.
к оглавлению
В чем разница между HashMap
и IdentityHashMap
? Для чего нужна IdentityHashMap
?
IdentityHashMap
— это структура данных, так же реализующая интерфейс Map
и использующая при сравнении ключей (значений) сравнение ссылок, а не вызов метода equals()
. Другими словами, в IdentityHashMap
два ключа k1
и k2
будут считаться равными, если они указывают на один объект, т.е. выполняется условие k1
== k2
.
IdentityHashMap
не использует метод hashCode()
, вместо которого применяется метод System.identityHashCode()
, по этой причине IdentityHashMap
по сравнению с HashMap
имеет более высокую производительность, особенно если последний хранит объекты с дорогостоящими методами equals()
и hashCode()
.
Одним из основных требований к использованию HashMap
является неизменяемость ключа, а, т.к. IdentityHashMap
не использует методы equals()
и hashCode()
, то это правило на него не распространяется.
IdentityHashMap
может применяться для реализации сериализации/клонирования. При выполнении подобных алгоритмов программе необходимо обслуживать хэш-таблицу со всеми ссылками на объекты, которые уже были обработаны. Такая структура не должна рассматривать уникальные объекты как равные, даже если метод equals()
возвращает true
.
Пример кода:
import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; public class Q2 { public static void main(String[] args) { Q2 q = new Q2(); q.testHashMapAndIdentityHashMap(); } private void testHashMapAndIdentityHashMap() { CreditCard visa = new CreditCard("VISA", "04/12/2019"); Map<CreditCard, String> cardToExpiry = new HashMap<>(); Map<CreditCard, String> cardToExpiryIdenity = new IdentityHashMap<>(); System.out.println("adding to HM"); // inserting objects to HashMap cardToExpiry.put(visa, visa.getExpiryDate()); // inserting objects to IdentityHashMap cardToExpiryIdenity.put(visa, visa.getExpiryDate()); System.out.println("adding to IHM"); System.out.println("before modifying keys"); String result = cardToExpiry.get(visa) != null ? "Yes" : "No"; System.out.println("Does VISA card exists in HashMap? " + result); result = cardToExpiryIdenity.get(visa) != null ? "Yes" : "No"; System.out.println("Does VISA card exists in IdenityHashMap? " + result); // modifying value object visa.setExpiryDate("02/11/2030"); System.out.println("after modifying keys"); result = cardToExpiry.get(visa) != null ? "Yes" : "No"; System.out.println("Does VISA card exists in HashMap? " + result); result = cardToExpiryIdenity.get(visa) != null ? "Yes" : "No"; System.out.println("Does VISA card exists in IdenityHashMap? " + result); System.out.println("cardToExpiry.containsKey"); System.out.println(cardToExpiry.containsKey(visa)); System.out.println("cardToExpiryIdenity.containsKey"); System.out.println(cardToExpiryIdenity.containsKey(visa)); } } class CreditCard { private String issuer; private String expiryDate; public CreditCard(String issuer, String expiryDate) { this.issuer = issuer; this.expiryDate = expiryDate; } public String getIssuer() { return issuer; } public String getExpiryDate() { return expiryDate; } public void setExpiryDate(String expiry) { this.expiryDate = expiry; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((expiryDate == null) ? 0 : expiryDate.hashCode()); result = prime * result + ((issuer == null) ? 0 : issuer.hashCode()); System.out.println("hashCode = " + result); return result; } @Override public boolean equals(Object obj) { System.out.println("equals !!! "); if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; CreditCard other = (CreditCard) obj; if (expiryDate == null) { if (other.expiryDate != null) return false; } else if (!expiryDate.equals(other.expiryDate)) return false; if (issuer == null) { if (other.issuer != null) return false; } else if (!issuer.equals(other.issuer)) return false; return true; } }
Результат выполнения кода:
adding to HM
hashCode = 1285631513
adding to IHM
before modifying keys
hashCode = 1285631513
Does VISA card exists in HashMap? Yes
Does VISA card exists in IdenityHashMap? Yes
after modifying keys
hashCode = 791156485
Does VISA card exists in HashMap? No
Does VISA card exists in IdenityHashMap? Yes
cardToExpiry.containsKey
hashCode = 791156485
false
cardToExpiryIdenity.containsKey
true
к оглавлению
В чем разница между HashMap
и WeakHashMap
? Для чего используется WeakHashMap
?
В Java существует 4 типа ссылок: сильные (strong reference), мягкие (SoftReference), слабые (WeakReference) и фантомные (PhantomReference). Особенности каждого типа ссылок связаны с работой Garbage Collector. Если объект можно достичь только с помощью цепочки WeakReference (то есть на него отсутствуют сильные и мягкие ссылки), то данный объект будет помечен на удаление.
WeakHashMap
— это структура данных, реализующая интерфейс Map
и основанная на использовании WeakReference для хранения ключей. Таким образом, пара «ключ-значение» будет удалена из WeakHashMap
, если на объект-ключ более не имеется сильных ссылок.
В качестве примера использования такой структуры данных можно привести следующую ситуацию: допустим имеются объекты, которые необходимо расширить дополнительной информацией, при этом изменение класса этих объектов нежелательно либо невозможно. В этом случае добавляем каждый объект в WeakHashMap
в качестве ключа, а в качестве значения — нужную информацию. Таким образом, пока на объект имеется сильная ссылка (либо мягкая), можно проверять хэш-таблицу и извлекать информацию. Как только объект будет удален, то WeakReference для этого ключа будет помещен в ReferenceQueue и затем соответствующая запись для этой слабой ссылки будет удалена из WeakHashMap
.
к оглавлению
В WeakHashMap
используются WeakReferences. А почему бы не создать SoftHashMap
на SoftReferences?
SoftHashMap
представлена в сторонних библиотеках, например, в Apache Commons
.
к оглавлению
В WeakHashMap
используются WeakReferences. А почему бы не создать PhantomHashMap
на PhantomReferences?
PhantomReference при вызове метода get()
возвращает всегда null
, поэтому тяжело представить назначение такой структуры данных.
к оглавлению
LinkedHashMap
— что в нем от LinkedList
, а что от HashMap
?
Реализация LinkedHashMap
отличается от HashMap
поддержкой двухсвязанного списка, определяющего порядок итерации по элементам структуры данных. По умолчанию элементы списка упорядочены согласно их порядку добавления в LinkedHashMap
(insertion-order). Однако порядок итерации можно изменить, установив параметр конструктора accessOrder
в значение true
. В этом случае доступ осуществляется по порядку последнего обращения к элементу (access-order). Это означает, что при вызове методов get()
или put()
элемент, к которому обращаемся, перемещается в конец списка.
При добавлении элемента, который уже присутствует в LinkedHashMap
(т.е. с одинаковым ключом), порядок итерации по элементам не изменяется.
к оглавлению
В чем проявляется «сортированность» SortedMap
, кроме того, что toString()
выводит все элементы по порядку?
Так же оно проявляется при итерации по коллекции.
к оглавлению
Как устроен HashMap
?
HashMap
состоит из «корзин» (bucket). С технической точки зрения «корзины» — это элементы массива, которые хранят ссылки на списки элементов. При добавлении новой пары «ключ-значение», вычисляет хэш-код ключа, на основании которого вычисляется номер корзины (номер ячейки массива), в которую попадет новый элемент. Если корзина пустая, то в нее сохраняется ссылка на вновь добавляемый элемент, если же там уже есть элемент, то происходит последовательный переход по ссылкам между элементами в цепочке, в поисках последнего элемента, от которого и ставится ссылка на вновь добавленный элемент. Если в списке был найден элемент с таким же ключом, то он заменяется.
к оглавлению
Согласно Кнуту и Кормену существует две основных реализации хэш-таблицы: на основе открытой адресации и на основе метода цепочек. Как реализована HashMap
? Почему, по вашему мнению, была выбрана именно эта реализация? В чем плюсы и минусы каждого подхода?
HashMap
реализован с использованием метода цепочек, т.е. каждой ячейке массива (корзине) соответствует свой связный список и при возникновении коллизии осуществляется добавление нового элемента в этот список.
Для метода цепочек коэффициент заполнения может быть больше 1 и с увеличением числа элементов производительность убывает линейно. Такие таблицы удобно использовать, если заранее неизвестно количество хранимых элементов, либо их может быть достаточно много, что приводит к большим значениям коэффициента заполнения.
Среди методов открытой реализации различают:
- линейное пробирование;
- квадратичное пробирование;
- двойное хэширование.
Недостатки структур с методом открытой адресации:
- Количество элементов в хэш-таблице не может превышать размера массива. По мере увеличения числа элементов и повышения коэффициента заполнения производительность структуры резко падает, поэтому необходимо проводить перехэширование.
- Сложно организовать удаление элемента.
- Первые два метода открытой адресации приводят к проблеме первичной и вторичной группировок.
Преимущества хэш-таблицы с открытой адресацией:
- отсутствие затрат на создание и хранение объектов списка;
- простота организации сериализации/десериализации объекта.
к оглавлению
Как работает HashMap
при попытке сохранить в него два элемента по ключам с одинаковым hashCode()
, но для которых equals() == false
?
По значению hashCode()
вычисляется индекс ячейки массива, в список которой этот элемент будет добавлен. Перед добавлением осуществляется проверка на наличие элементов в этой ячейке. Если элементы с таким hashCode()
уже присутствует, но их equals()
методы не равны, то элемент будет добавлен в конец списка.
к оглавлению
Какое начальное количество корзин в HashMap
?
В конструкторе по умолчанию — 16, используя конструкторы с параметрами можно задавать произвольное начальное количество корзин.
к оглавлению
Какова оценка временной сложности операций над элементами из HashMap
? Гарантирует ли HashMap
указанную сложность выборки элемента?
В общем случае операции добавления, поиска и удаления элементов занимают константное время.
Данная сложность не гарантируется, т.к. если хэш-функция распределяет элементы по корзинам равномерно, временная сложность станет не хуже Логарифмического времени O(log(N)), а в случае, когда хэш-функция постоянно возвращает одно и то же значение, HashMap
превратится в связный список со сложностью О(n).
Пример кода двоичного поиска:
public class Q { public static void main(String[] args) { Q q = new Q(); q.binSearch(); } private void binSearch() { int[] inpArr = {1, 2, 3, 4, 5, 6, 7, 8, 9}; Integer result = binSearchF(inpArr, 1, 0, inpArr.length - 1); System.out.println("-----------------------"); result = binSearchF(inpArr, 2, 0, inpArr.length - 1); System.out.println("Found at position " + result); } private Integer binSearchF(int[] inpArr, int searchValue, int low, int high) { Integer index = null; while (low <= high) { System.out.println("New iteration, low = " + low + ", high = " + high); int mid = (low + high) / 2; System.out.println("trying mid = " + mid + " inpArr[mid] = " + inpArr[mid]); if (inpArr[mid] < searchValue) { low = mid + 1; System.out.println("inpArr[mid] (" + inpArr[mid] + ") < searchValue(" + searchValue + "), mid = " + mid + ", setting low = " + low); } else if (inpArr[mid] > searchValue) { high = mid - 1; System.out.println("inpArr[mid] (" + inpArr[mid] + ") > searchValue(" + searchValue + "), mid = " + mid + ", setting high = " + high); } else if (inpArr[mid] == searchValue) { index = mid; System.out.println("found at index " + mid); break; } } return index; } }
к оглавлению
Возможна ли ситуация, когда HashMap
выродится в список даже с ключами имеющими разные hashCode()
?
Это возможно в случае, если метод, определяющий номер корзины будет возвращать одинаковые значения.
к оглавлению
В каком случае может быть потерян элемент в HashMap
?
Допустим, в качестве ключа используется не примитив, а объект с несколькими полями. После добавления элемента в HashMap
у объекта, который выступает в качестве ключа, изменяют одно поле, которое участвует в вычислении хэш-кода. В результате при попытке найти данный элемент по исходному ключу, будет происходить обращение к правильной корзине, а вот equals
уже не найдет указанный ключ в списке элементов. Тем не менее, даже если equals
реализован таким образом, что изменение данного поля объекта не влияет на результат, то после увеличения размера корзин и пересчета хэш-кодов элементов, указанный элемент, с измененным значением поля, с большой долей вероятности попадет в совершенно другую корзину и тогда уже потеряется совсем.
к оглавлению
Почему нельзя использовать byte[]
в качестве ключа в HashMap
?
Хэш-код массива не зависит от хранимых в нем элементов, а присваивается при создании массива (метод вычисления хэш-кода массива не переопределен и вычисляется по стандартному Object.hashCode()
на основании адреса массива). Так же у массивов не переопределен equals
и выполняется сравнение указателей. Это приводит к тому, что обратиться к сохраненному с ключом-массивом элементу не получится при использовании другого массива такого же размера и с такими же элементами, доступ можно осуществить лишь в одном случае — при использовании той же самой ссылки на массив, что использовалась для сохранения элемента.
к оглавлению
Какова роль equals()
и hashCode()
в HashMap
?
hashCode
позволяет определить корзину для поиска элемента, а equals
используется для сравнения ключей элементов в списке корзины и искомого ключа.
к оглавлению
Каково максимальное число значений hashCode()
?
Число значений следует из сигнатуры int hashCode()
и равно диапазону типа int
— 232.
к оглавлению
Какое худшее время работы метода get(key) для ключа, которого нет в HashMap
?
Какое худшее время работы метода get(key) для ключа, который есть в HashMap
?
O(N). Худший случай — это поиск ключа в HashMap
, вырожденного в список по причине совпадения ключей по hashCode()
и для выяснения хранится ли элемент с определённым ключом может потребоваться перебор всего списка.
Но начиная с Java 8, после определенного числа элементов в списке, связный список преобразовывается в красно-черное дерево и сложность выборки, даже в случае плохой хеш-функции, не хуже логарифмической O(log(N))
к оглавлению
Сколько переходов происходит в момент вызова HashMap.get(key)
по ключу, который есть в таблице?
- ключ равен
null
: 1 — выполняется единственный методgetForNullKey()
. - любой ключ отличный от
null
: 4 — вычисление хэш-кода ключа; определение номера корзины; поиск значения; возврат значения.
к оглавлению
Сколько создается новых объектов, когда вы добавляете новый элемент в HashMap
?
Один новый объект статического вложенного класса Entry<K,V>
.
к оглавлению
Как и когда происходит увеличение количества корзин в HashMap
?
Помимо capacity
у HashMap
есть еще поле loadFactor
, на основании которого, вычисляется предельное количество занятых корзин capacity * loadFactor
. По умолчанию loadFactor = 0.75
. По достижению предельного значения, число корзин увеличивается в 2 раза и для всех хранимых элементов вычисляется новое «местоположение» с учетом нового числа корзин.
к оглавлению
Объясните смысл параметров в конструкторе HashMap(int initialCapacity, float loadFactor)
.
initialCapacity
— исходный размерHashMap
, количество корзин в хэш-таблице в момент её создания.loadFactor
— коэффициент заполненияHashMap
, при превышении которого происходит увеличение количества корзин и автоматическое перехэширование. Равен отношению числа уже хранимых элементов в таблице к её размеру.
к оглавлению
Будет ли работать HashMap
, если все добавляемые ключи будут иметь одинаковый hashCode()
?
Да, будет, но в этом случае HashMap
вырождается в связный список и теряет свои преимущества.
Как перебрать все ключи Map
?
Использовать метод keySet()
, который возвращает множество Set<K>
ключей.
к оглавлению
Как перебрать все значения Map
?
Использовать метод values()
, который возвращает коллекцию Collection<V>
значений.
к оглавлению
Как перебрать все пары «ключ-значение» в Map
?
Использовать метод entrySet()
, который возвращает множество Set<Map.Entry<K, V>
пар «ключ-значение».
к оглавлению
В чем отличия TreeSet
и HashSet
?
TreeSet
обеспечивает упорядоченно хранение элементов в виде красно-черного дерева. Сложность выполнения основных операций не хуже O(log(N)) (Логарифмическое время).
HashSet
использует для хранения элементов такой же подход, что и HashMap
, за тем отличием, что в HashSet
в качестве ключа и значения выступает сам элемент
, кроме того, HashSet
не поддерживает упорядоченное хранение элементов и обеспечивает временную сложность выполнения операций аналогично HashMap
.
к оглавлению
Что будет, если добавлять элементы в TreeSet
по возрастанию?
В основе TreeSet
лежит красно-черное дерево, которое умеет само себя балансировать. В итоге, TreeSet
все равно в каком порядке вы добавляете в него элементы, преимущества этой структуры данных будут сохраняться.
к оглавлению
Чем LinkedHashSet
отличается от HashSet
?
LinkedHashSet
отличается от HashSet
только тем, что в его основе лежит LinkedHashMap
вместо HashMap
. Благодаря этому порядок элементов при обходе коллекции является идентичным порядку добавления элементов (insertion-order). При добавлении элемента, который уже присутствует в LinkedHashSet
(т.е. с одинаковым ключом), порядок обхода элементов не изменяется.
к оглавлению
Для Enum
есть специальный класс java.util.EnumSet
. Зачем? Чем авторов не устраивал HashSet
или TreeSet
?
EnumSet
— это реализация интерфейса Set
для использования с перечислениями (Enum
). В структуре данных хранятся объекты только одного типа Enum
, указываемого при создании. Для хранения значений EnumSet
использует массив битов (bit vector), — это позволяет получить высокую компактность и эффективность. Проход по EnumSet
осуществляется согласно порядку объявления элементов перечисления.
Все основные операции выполняются за O(1) и обычно (но негарантированно) быстрей аналогов из HashSet
, а пакетные операции (bulk operations), такие как containsAll()
и retainAll()
выполняются даже гораздо быстрей.
Помимо всего EnumSet
предоставляет множество статических методов инициализации для упрощенного и удобного создания экземпляров.
к оглавлению
Какие существуют способы перебирать элементы списка?
- Цикл с итератором
Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { //iterator.next(); }
- Цикл
for
for (int i = 0; i < list.size(); i++) { //list.get(i); }
- Цикл
while
int i = 0; while (i < list.size()) { //list.get(i); i++; }
- «for-each»
for (String element : list) { //element; }
к оглавлению
Каким образом можно получить синхронизированные объекты стандартных коллекций?
С помощью статических методов synchronizedMap()
и synchronizedList()
класса Collections
. Данные методы возвращают синхронизированный декоратор переданной коллекции. При этом все равно в случае обхода по коллекции требуется ручная синхронизация.
Map m = Collections.synchronizedMap(new HashMap()); List l = Collections.synchronizedList(new ArrayList());
Начиная с Java 6 JCF был расширен специальными коллекциями, поддерживающими многопоточный доступ, такими как CopyOnWriteArrayList
и ConcurrentHashMap
.
к оглавлению
Как получить коллекцию только для чтения?
При помощи:
Collections.unmodifiableList(list)
;Collections.unmodifiableSet(set)
;Collections.unmodifiableMap(map)
.
Эти методы принимают коллекцию в качестве параметра, и возвращают коллекцию только для чтения с теми же элементами внутри.
к оглавлению
Напишите однопоточную программу, которая заставляет коллекцию выбросить ConcurrentModificationException
.
public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); for (Integer integer : list) { list.remove(1); } }
к оглавлению
Приведите пример, когда какая-либо коллекция выбрасывает UnsupportedOperationException
.
public static void main(String[] args) { List<Integer> list = Collections.emptyList(); list.add(0); }
к оглавлению
Реализуйте симметрическую разность двух коллекций используя методы Collection
(addAll(...)
, removeAll(...)
, retainAll(...)
).
Симметрическая разность двух коллекций — это множество элементов, одновременно не принадлежащих обоим исходным коллекциям.
<T> Collection<T> symmetricDifference(Collection<T> a, Collection<T> b) { // Объединяем коллекции. Collection<T> result = new ArrayList<>(a); result.addAll(b); // Получаем пересечение коллекций. Collection<T> intersection = new ArrayList<>(a); intersection.retainAll(b); // Удаляем элементы, расположенные в обоих коллекциях. result.removeAll(intersection); return result; }
к оглавлению
Как, используя LinkedHashMap, сделать кэш c «invalidation policy»?
Необходимо использовать LRU-алгоритм (Least Recently Used algorithm) и LinkedHashMap
с access-order. В этом случае при обращении к элементу он будет перемещаться в конец списка, а наименее используемые элементы будут постепенно группироваться в начале списка. Так же в стандартной реализации LinkedHashMap
есть метод removeEldestEntries()
, который возвращает true
, если текущий объект LinkedHashMap
должен удалить наименее используемый элемент из коллекции при использовании методов put()
и putAll()
.
public class LRUCache<K, V> extends LinkedHashMap<K, V> { private static final int MAX_ENTRIES = 10; public LRUCache(int initialCapacity) { super(initialCapacity, 0.85f, true); } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > MAX_ENTRIES; } }
Стоит заметить, что LinkedHashMap
не позволяет полностью реализовать LRU-алгоритм, поскольку при вставке уже имеющегося в коллекции элемента порядок итерации по элементам не меняется.
к оглавлению
Как одной строчкой скопировать элементы любой collection
в массив?
Object[] array = collection.toArray();
к оглавлению
Как одним вызовом из List
получить List
со всеми элементами, кроме первых и последних 3-х?
List<Integer> subList = list.subList(3, list.size() - 3);
к оглавлению
Как одной строчкой преобразовать HashSet
в ArrayList
?
ArrayList<Integer> list = new ArrayList<>(new HashSet<>());
к оглавлению
Как одной строчкой преобразовать ArrayList
в HashSet
?
HashSet<Integer> set = new HashSet<>(new ArrayList<>());
к оглавлению
Сделайте HashSet
из ключей HashMap
.
HashSet<Object> set = new HashSet<>(map.keySet());
к оглавлению
Сделайте HashMap
из HashSet<Map.Entry<K, V>>
.
HashMap<K, V> map = new HashMap<>(set.size()); for (Map.Entry<K, V> entry : set) { map.put(entry.getKey(), entry.getValue()); }
к оглавлению
Источник
- parshinpn.pro
- Хабрахабр
- Quizful
- JavaRush
- Хабрахабр:Справочник по Java Collections Framework
Вопросы для собеседования
O(N). Время поиска элемента линейно пропорционально количеству элементов с списке.
O(N). Здесь стоит заметить, что добавление элемента в конец списка с помощью методом add(value), addLast(value) и добавление в начало списка с помощью addFirst(value) выполняется за время O(1).
O(N) — будет при добавление элемента в отсортированный список, а также при добавлении элемента с помощью метода add(index, value).
O(N). Вставка элемента в конец списка осуществляется за время O(1), но если вместимость массива недостаточна, то происходит создание нового массива с увеличенным размером и копирование всех элементов из старого массива в новый.
Если в массиве достаточно места для размещения нового элемента, то дополнительное место в памяти не выделяется. Иначе происходит создание нового массива с размером:
Другими словами, создается новый массив, размер которого вычисляется как умножение старого размера на 1.5 (это верно для JDK 1.7, в более ранних версиях вычисления отличаются).
- Сколько выделяется элементов в памяти при вызове LinkedList.add()?
Создается один новый экземпляр вложенного класса Node.
- Оцените количество памяти на хранение одного примитива типа byte в LinkedList?
Каждый элемент LinkedList хранит ссылку на предыдущий элемент, следующий элемент и ссылку на данные. Для x32 систем каждая ссылка занимает 32 бита (4 байта). Сам объект типа Node занимает приблизительно 8 байт. Размер каждого объекта в Java кратен 8, соответственно получаем 24 байта. Примитив типа byte занимает 1 байт памяти, но в списке примитивы упаковываются, соответственно получаем еще 8 байт. Таким образом, в x32 JVM около 32 байтоввыделяется для хранения одного значения типа byte в LinkedList.
Для 64-битной JVM каждая ссылка занимает 64 бита (8 байт). Вычисления аналогичны.
- Оцените количество памяти на хранение одного примитива типа byte в ArrayList?
ArrayList основан на массиве. Каждый элемент массива хранит примитивный тип данных — byte, размер которого 1 байт.
- Я добавляю элемент в середину List-а: list.add(list.size()/2, newElem). Для кого эта операция медленнее — для ArrayList или для LinkedList?
Для ArrayList:
- проверка массива на вместимость. Если вместимости недостаточно, то увеличение размера массива и копирование всех элементов в новый массив ( O(N) );
- копирование всех элементов, расположенных правее от позиции вставки, на одну позицию вправо ( O(N/2));
- вставка элемента ( O(1) ).
Для LinkedList:
- поиск позиции вставки ( O(N/2) );
- вставка элемента ( O(1) ).
В худшем случае вставка в середину списка эффективнее для LinkedList. В остальных — скорее всего, для ArrayList, поскольку копирование элементов осуществляется за счет системного метода System.arraycopy().
- Как перебрать элементы LinkedList в обратном порядке, не используя медленный get(index)?
Использовать обратный итератор. Для этого в LinkedList есть метод descendingIterator().
- Как одним вызовом из List получить List со всеми элементами, кроме первых и последних 3-х?
- Могут ли у разных объектов в памяти (ref0 != ref1) быть ref0.hashCode() == ref1.hashCode()?
Да, могут. Метод hashCode() не гарантирует уникальность возвращаемого значения.
- Могут ли у разных объектов в памяти (ref0 != ref1) быть ref0.equals(ref1) == true?
Да, могут. Для этого в классе этих объектов должен быть переопределен метод equals().
Если используется метод Object.equals(), то для двух ссылок x и y метод вернет true тогда и только тогда, когда обе ссылки указывают на один и тот же объект (т.е. x == y возвращает true).
- Могут ли у разных ссылок на один объект в памяти (ref0 == ref1) быть ref0.equals(ref1) == false?
Нет, не может. Метод equals() должен гарантировать свойство рефлексивности: для любых ненулевых ссылок xметод x.equals(x) должен возвращать true.
- Есть класс Point{int x, y;}. Почему хэш-код в виде 31 * x + y предпочтительнее чем x + y?
Множитель создает зависимость значения хэш-кода от очередности обработки полей, а это дает гораздо лучшую хэш-функцию.
- Если у класса Point{int x, y;} «правильно» реализовать метод equals (return ref0.x == ref1.x && ref0.y == ref1.y), но сделать хэш-код в виде int hashCode() {return x;}, то будут ли корректно такие точки помещаться и извлекаться из HashSet?
HashSet использует HashMap для хранения элементов (в качестве ключа используется сам объект). При добавлении элемента в HashMap вычисляется хэшкод и позиция в массиве, куда будет вставлен новый элемент. У всех экземпляров класса Point одинаковый хэшкод, что приводит в вырождению хэш-таблицы в список. При возникновении коллизии осуществляется проверка на наличие уже такого элемента в текущем списке:
Если элемент найден, то его значение перезаписывается. В нашем случае для разных объектов метод equals() будет возвращать false. Соответственно новый элемент будет добавлен в HashSet. Извлечение элемента также будет осуществляться успешно.
Но производительность такого кода будет низкой и преимущества хэш-таблиц использоваться не будут.
- equals() порождает отношение эквивалентности. Какими из свойств обладает такое отношение: коммутативность, симметричность, рефлексивность, дистрибутивность, ассоциативность, транзитивность?
Метод equals() должен обеспечивать:
- симметричность (для любых ненулевых ссылок x и y метод x.equals(y) должен возвращать true тогда и только тогда, когда y.equals(x) возвращает true);
- рефлексивность (для любых ненулевых ссылок x метод x.equals(x) должен возвращать true.);
- транзитивность (для любых ненулевых ссылок x, y и z, если x.equals(y) возвращает true и y.equals(z)возвращает true, тогда и x.equals(z) должен возвращать true).
Также есть ещё два свойства: постоянство и неравенство null.
- Можно ли так реализовать equals(Object that) {return this.hashCode() == that.hashCode()}?
Строго говоря нельзя, поскольку метод hashCode() не гарантирует уникальность значения для каждого объекта. Однако для сравнения экземпляров класса Object такой код допустим, т.к. метод hashCode() в классе Object возвращает уникальные значения для разных объектов (вычисления основаны на использовании адреса объекта в памяти).
- В equals требуется проверять, что аргумент (equals(Object that)) такого же типа как и сам объект. В чем разница между this.getClass() == that.getClass() и that instanceof MyClass?
Оператор instanceof сравнивает объект и указанный тип. Его можно использовать для проверки является ли данный объект экземпляром некоторого класса, либо экземпляром его дочернего класса, либо экземпляром класса, который реализует указанный интерфейс.
getClass() = … проверяет два типа на идентичность.
Для корректной реализации контракта метода equals() необходимо использовать точное сравнение с помощью getClass().
- Можно ли реализовать метод equals класса MyClass вот так: class MyClass {public boolean equals(MyClass that) {return this == that;}}?
Реализовать можно, но данный метод не переопределяет метод equals() класса Object, а перегружает его.
- Будет ли работать HashMap, если все ключи будут возвращать int hashCode() {return 42;}?
Да, будет. Но тогда хэш-таблица вырождается в связный список и теряет свои преимущества.
- Зачем добавили HashMap, если уже был Hashtable?
Класс Hashtable был введен в JDK 1.0 и не является частью Java Collection Framework. Методы класса Hashtable синхронизированы, что обеспечивает потокобезопасность, но это приводит к снижению производительности, поэтому и был введен класс HashMap, методы которого не синхронизированы.
Помимо этого класс HashMap обладает некоторыми другими отличиями: например, позволяет хранить один null ключ и множество null значений.
- Согласно Кнуту и Кормену существует две основных реализации хэш-таблицы: на основе открытой адресацией и на основе метода цепочек. Как реализована HashMap? Почему так сделали (по вашему мнению)? В чем минусы и плюсы каждого подхода?
Класс HashMap реализован с использованием метода цепочек, т.е. каждой ячейке массива соответствует свой связный список и при возникновении коллизии осуществляется добавление нового элемента в этот список.
Для метода цепочек коэффициент заполнения может быть больше 1, с увеличением числа элементов производительность убывает линейно. Такие таблицы удобно использовать, если заранее неизвестно количество хранимых элементов, либо их может быть достаточно много, что приводит к большим значениям коэффициента заполнения.
Среди методов открытой реализации различают:
- линейное пробирование;
- квадратичное пробирование;
- двойное хеширование.
Основные недостатки структур с методом открытой адресации:
- Количество элементов в таблице не может превышать размера массива. По мере увеличения числа элементов в таблице и повышения коэффициента заполнения (load factor) производительность структуры резко падает, поэтому необходимо проводить перехеширование.
- Сложно организовать удаление элемента.
- Также первые два метода открытой адресации приводят к проблеме первичной и вторичной группировок.
Основное преимущество хэш-таблицы с открытой адресацией — это отсутствие затрат на создание и хранение объектов списка. Также проще организовать сериализацию/десериализацию объекта.
- Сколько переходов по ссылкам происходит, когда вы делаете HashMap.get(key) по ключу, который есть в таблице?
Возможно, я неправильно понял этот вопрос. За переходы по ссылке в данном ответе я считаю вызовы методов.
Рассмотрим первый случай, когда ключ равен null: выполняем метод getForNullKey().
В цикле foreach проходимся по списку значений для ключа и возвращаем нужное значение. Таким образом, получаем 1 переход.
Второй случай: ключ не равен null. Выполняем метод getEntry(key).
Вычисляется хэш-код ключа (метод hash(key)), затем определяется индекс ячейки массива, в которой будем искать значение (метод indexFor(hash, table.length)).
После того, как нашли нужную пару «ключ-значение» возвращаем значение (метод entry.getValue()). Таким образом, получаем 4 перехода.
- Сколько создается новых объектов, когда вы добавляете новый элемент в HashMap?
- Сколько создается новых объектов, когда вы добавляете новый элемент в HashMap?
Один новый объект статического вложенного класса Entry<K,V>.
- Как работает HashMap при попытке сохранить в нее два элемента по ключам с одинаковым hashCode, но для которых equals == false?
По значению hashCode вычисляется индекс ячейки массива, в список которой будет происходить добавление элемента. Перед добавлением осуществляется проверка на наличие уже элементов в этой ячейке. Если элементов нет, то происходит добавление. Если возникает коллизия, то итеративно осуществляется обход списка в поисках элемента с таким же ключом и хэш-кодом. Если такой элемент найден, то его значение перезаписывается, а старое — возвращается. Поскольку в условии сказано, что добавляемые ключи — разные, то второй элемент будет добавлен в начало списка.
- HashMap может выродиться в список даже для ключей с разным hashCode. Как это возможно?
Это возможно в случае, если метод, определяющий номер ячейки массива по hashCode будет возвращать одинаковое значение.
- Какое худшее время работы метода get(key) для ключа, которого нет в таблице (O(1), O(log(N)), O(N), O(N*log(N)), O(N*N))?
O(N). Худший случай — это поиск ключа в таблице, вырожденной в список, перебор ключей которой занимает линейно пропорциональное время количеству хранимых элементов.
- Какое худшее время работы метода get(key) для ключа, который есть в таблице (O(1), O(log(N)), O(N), O(N*log(N)), O(N*N))?
O(N). Аналогичные рассуждения, что и для предыдущего вопроса.
- Объясните смысл параметров в конструкторе HashMap(int initialCapacity, float loadFactor).
int initialCapacity — исходный размер HashMap (количество корзин в хэш-таблице в момент её создания), по умолчанию имеет значение 16.
float loadFactor — коэффициент заполнения HashMap. Равен отношению числа хранимых элементов в таблице к её размеру. loadFactor — является мерой заполнения таблицы элементами, при превышении количества хранимых таблицей значений , происходит автоматическое перехеширование. Значение по умолчанию 0.75 является хорошим компромиссом между временем доступа и объемом хранимых данных.
Java Collections Framework (часть 3).
Вопросы для собеседования
Java Collections Framework
- Что такое «коллекция»?
- Назовите основные интерфейсы JCF и их реализации.
- Расположите в виде иерархии следующие интерфейсы:
List
,Set
,Map
,SortedSet
,SortedMap
,Collection
,Iterable
,Iterator
,NavigableSet
,NavigableMap
. - Почему
Map
— это неCollection
, в то время какList
иSet
являютсяCollection
? - В чем разница между классами
java.util.Collection
иjava.util.Collections
? - Что такое «fail-fast поведение»?
- Какая разница между fail-fast и fail-safe?
- Приведите примеры итераторов, реализующих поведение fail-safe
- Чем различаются
Enumeration
иIterator
. - Как между собой связаны
Iterable
иIterator
? - Как между собой связаны
Iterable
,Iterator
и «for-each»? - Сравните
Iterator
иListIterator
. - Что произойдет при вызове
Iterator.next()
без предварительного вызоваIterator.hasNext()
? - Сколько элементов будет пропущено, если
Iterator.next()
будет вызван после 10 вызововIterator.hasNext()
? - Как поведёт себя коллекция, если вызвать
iterator.remove()
? - Как поведёт себя уже инстанциированный итератор для
collection
, если вызватьcollection.remove()
? - Как избежать
ConcurrentModificationException
во время перебора коллекции? - Какая коллекция реализует дисциплину обслуживания FIFO?
- Какая коллекция реализует дисциплину обслуживания FILO?
- Чем отличается
ArrayList
отVector
? - Зачем добавили
ArrayList
, если уже былVector
? - Чем отличается
ArrayList
отLinkedList
? В каких случаях лучше использовать первый, а в каких второй? - Что работает быстрее
ArrayList
илиLinkedList
? - Какое худшее время работы метода
contains()
для элемента, который есть вLinkedList
? - Какое худшее время работы метода
contains()
для элемента, который есть вArrayList
? - Какое худшее время работы метода
add()
дляLinkedList
? - Какое худшее время работы метода
add()
дляArrayList
? - Необходимо добавить 1 млн. элементов, какую структуру вы используете?
- Как происходит удаление элементов из
ArrayList
? Как меняется в этом случае размерArrayList
? - Предложите эффективный алгоритм удаления нескольких рядом стоящих элементов из середины списка, реализуемого
ArrayList
. - Сколько необходимо дополнительной памяти при вызове
ArrayList.add()
? - Сколько выделяется дополнительно памяти при вызове
LinkedList.add()
? - Оцените количество памяти на хранение одного примитива типа
byte
вLinkedList
? - Оцените количество памяти на хранение одного примитива типа
byte
вArrayList
? - Для
ArrayList
или дляLinkedList
операция добавления элемента в середину (list.add(list.size()/2, newElement)
) медленнее? - В реализации класса
ArrayList
есть следующие поля:Object[] elementData
,int size
. Объясните, зачем хранить отдельноsize
, если всегда можно взятьelementData.length
? - Сравните интерфейсы
Queue
иDeque
. - Кто кого расширяет:
Queue
расширяетDeque
, илиDeque
расширяетQueue
? - Почему
LinkedList
реализует иList
, иDeque
? LinkedList
— это односвязный, двусвязный или четырехсвязный список?- Как перебрать элементы
LinkedList
в обратном порядке, не используя медленныйget(index)
? - Что позволяет сделать
PriorityQueue
? Stack
считается «устаревшим». Чем его рекомендуют заменять? Почему?- Зачем нужен
HashMap
, если естьHashtable
? - В чем разница между
HashMap
иIdentityHashMap
? Для чего нужнаIdentityHashMap
? - В чем разница между
HashMap
иWeakHashMap
? Для чего используетсяWeakHashMap
? - В
WeakHashMap
используются WeakReferences. А почему бы не создатьSoftHashMap
на SoftReferences? - В
WeakHashMap
используются WeakReferences. А почему бы не создатьPhantomHashMap
на PhantomReferences? LinkedHashMap
— что в нем отLinkedList
, а что отHashMap
?- В чем проявляется «сортированность»
SortedMap
, кроме того, чтоtoString()
выводит все элементы по порядку? - Как устроен
HashMap
? - Согласно Кнуту и Кормену существует две основных реализации хэш-таблицы: на основе открытой адресации и на основе метода цепочек. Как реализована
HashMap
? Почему, по вашему мнению, была выбрана именно эта реализация? В чем плюсы и минусы каждого подхода? - Как работает
HashMap
при попытке сохранить в него два элемента по ключам с одинаковымhashCode()
, но для которыхequals() == false
? - Какое начальное количество корзин в
HashMap
? - Какова оценка временной сложности операций над элементами из
HashMap
? Гарантирует лиHashMap
указанную сложность выборки элемента? - Возможна ли ситуация, когда
HashMap
выродится в список даже с ключами имеющими разныеhashCode()
? - В каком случае может быть потерян элемент в
HashMap
? - Почему нельзя использовать
byte[]
в качестве ключа вHashMap
? - Какова роль
equals()
иhashCode()
вHashMap
? - Каково максимальное число значений
hashCode()
? - Какое худшее время работы метода get(key) для ключа, которого нет в
HashMap
? - Какое худшее время работы метода get(key) для ключа, который есть в
HashMap
? - Сколько переходов происходит в момент вызова
HashMap.get(key)
по ключу, который есть в таблице? - Сколько создается новых объектов, когда вы добавляете новый элемент в
HashMap
? - Как и когда происходит увеличение количества корзин в
HashMap
? - Объясните смысл параметров в конструкторе
HashMap(int initialCapacity, float loadFactor)
. - Будет ли работать
HashMap
, если все добавляемые ключи будут иметь одинаковыйhashCode()
? - Как перебрать все ключи
Map
? - Как перебрать все значения
Map
? - Как перебрать все пары «ключ-значение» в
Map
? - В чем отличия
TreeSet
иHashSet
? - Что будет, если добавлять элементы в
TreeSet
по возрастанию? - Чем
LinkedHashSet
отличается отHashSet
? - Для
Enum
есть специальный классjava.util.EnumSet
. Зачем? Чем авторов не устраивалHashSet
илиTreeSet
? - Какие существуют способы перебирать элементы списка?
- Каким образом можно получить синхронизированные объекты стандартных коллекций?
- Как получить коллекцию только для чтения?
- Напишите однопоточную программу, которая заставляет коллекцию выбросить
ConcurrentModificationException
. - Приведите пример, когда какая-либо коллекция выбрасывает
UnsupportedOperationException
. - Реализуйте симметрическую разность двух коллекций используя методы
Collection
(addAll(...)
,removeAll(...)
,retainAll(...)
). - Как, используя LinkedHashMap, сделать кэш c «invalidation policy»?
- Как одной строчкой скопировать элементы любой
collection
в массив? - Как одним вызовом из
List
получитьList
со всеми элементами, кроме первых и последних 3-х? - Как одной строчкой преобразовать
HashSet
вArrayList
? - Как одной строчкой преобразовать
ArrayList
вHashSet
? - Сделайте
HashSet
из ключейHashMap
. - Сделайте
HashMap
изHashSet<Map.Entry<K, V>>
.
Что такое «коллекция»?
«Коллекция» — это структура данных, набор каких-либо объектов. Данными (объектами в наборе) могут быть числа, строки, объекты пользовательских классов и т.п.
к оглавлению
Назовите основные интерфейсы JCF и их реализации.
На вершине иерархии в Java Collection Framework располагаются 2 интерфейса: Collection
и Map
. Эти интерфейсы разделяют все коллекции, входящие во
фреймворк на две части по типу хранения данных: простые последовательные наборы элементов и наборы пар «ключ — значение» соответственно.
Интерфейс Collection
расширяют интерфейсы:
List
(список) представляет собой коллекцию, в которой допустимы дублирующие значения. Элементы такой коллекции пронумерованы, начиная от нуля, к ним можно обратиться по индексу. Реализации:ArrayList
— инкапсулирует в себе обычный массив, длина которого автоматически увеличивается при добавлении новых элементов.LinkedList
(двунаправленный связный список) — состоит из узлов, каждый из которых содержит как собственно данные, так и две ссылки на следующий и предыдущий узел.Vector
— реализация динамического массива объектов, методы которой синхронизированы.Stack
— реализация стека LIFO (last-in-first-out).
Set
(сет) описывает неупорядоченную коллекцию, не содержащую повторяющихся элементов. Реализации:HashSet
— использует HashMap для хранения данных. В качестве ключа и значения используется добавляемый элемент. Из-за особенностей реализации порядок элементов не гарантируется при добавлении.LinkedHashSet
— гарантирует, что порядок элементов при обходе коллекции будет идентичен порядку добавления элементов.TreeSet
— предоставляет возможность управлять порядком элементов в коллекции при помощи объектаComparator
, либо сохраняет элементы с использованием «natural ordering».
Queue
(очередь) предназначена для хранения элементов с предопределённым способом вставки и извлечения FIFO (first-in-first-out):PriorityQueue
— предоставляет возможность управлять порядком элементов в коллекции при помощи объектаComparator
, либо сохраняет элементы с использованием «natural ordering».ArrayDeque
— реализация интерфейсаDeque
, который расширяет интерфейсQueue
методами, позволяющими реализовать конструкцию вида LIFO (last-in-first-out).
Интерфейс Map
реализован классами:
Hashtable
— хэш-таблица, методы которой синхронизированы. Не позволяет использоватьnull
в качестве значения или ключа и не является упорядоченной.HashMap
— хэш-таблица. Позволяет использоватьnull
в качестве значения или ключа и не является упорядоченной.LinkedHashMap
— упорядоченная реализация хэш-таблицы.TreeMap
— реализация, основанная на красно-чёрных деревьях. Является упорядоченной и предоставляет возможность управлять порядком элементов в коллекции при помощи объектаComparator
, либо сохраняет элементы с использованием «natural ordering».WeakHashMap
— реализация хэш-таблицы, которая организована с использованием weak references для ключей (сборщик мусора автоматически удалит элемент из коллекции при следующей сборке мусора, если на ключ этого элемента нет жёстких ссылок).
к оглавлению
Расположите в виде иерархии следующие интерфейсы: List
, Set
, Map
, SortedSet
, SortedMap
, Collection
, Iterable
, Iterator
, NavigableSet
, NavigableMap
.
Iterable
Collection
List
Set
SortedSet
NavigableSet
Map
SortedMap
NavigableMap
Iterator
к оглавлению
Почему Map
— это не Collection
, в то время как List
и Set
являются Collection
?
Collection
представляет собой совокупность некоторых элементов. Map
— это совокупность пар «ключ-значение».
к оглавлению
В чем разница между классами java.util.Collection
и java.util.Collections
?
java.util.Collections
— набор статических методов для работы с коллекциями.
java.util.Collection
— один из основных интерфейсов Java Collections Framework.
к оглавлению
Что такое «fail-fast поведение»?
fail-fast поведение означает, что при возникновении ошибки или состояния, которое может привести к ошибке, система немедленно прекращает дальнейшую работу и уведомляет об этом. Использование fail-fast подхода позволяет избежать недетерминированного поведения программы в течение времени.
В Java Collections API некоторые итераторы ведут себя как fail-fast и выбрасывают ConcurrentModificationException
, если после его создания была произведена модификация коллекции, т.е. добавлен или удален элемент напрямую из коллекции, а не используя методы итератора.
Реализация такого поведения осуществляется за счет подсчета количества модификаций коллекции (modification count):
- при изменении коллекции счетчик модификаций так же изменяется;
- при создании итератора ему передается текущее значение счетчика;
- при каждом обращении к итератору сохраненное значение счетчика сравнивается с текущим, и, если они не совпадают, возникает исключение.
к оглавлению
Какая разница между fail-fast и fail-safe?
В противоположность fail-fast, итераторы fail-safe не вызывают никаких исключений при изменении структуры, потому что они работают с клоном коллекции вместо оригинала.
к оглавлению
Приведите примеры итераторов, реализующих поведение fail-safe
Итератор коллекции CopyOnWriteArrayList
и итератор представления keySet
коллекции ConcurrentHashMap
являются примерами итераторов fail-safe.
к оглавлению
Чем различаются Enumeration
и Iterator
.
Хотя оба интерфейса и предназначены для обхода коллекций между ними имеются существенные различия:
- с помощью
Enumeration
нельзя добавлять/удалять элементы; - в
Iterator
исправлены имена методов для повышения читаемости кода (Enumeration.hasMoreElements()
соответствуетIterator.hasNext()
,Enumeration.nextElement()
соответствуетIterator.next()
и т.д); Enumeration
присутствуют в устаревших классах, таких какVector
/Stack
, тогда какIterator
есть во всех современных классах-коллекциях.
к оглавлению
Как между собой связаны Iterable
и Iterator
?
Интерфейс Iterable
имеет только один метод — iterator()
, который возвращает Iterator
.
к оглавлению
Как между собой связаны Iterable
, Iterator
и «for-each»?
Классы, реализующие интерфейс Iterable
, могут применяться в конструкции for-each
, которая использует Iterator
.
к оглавлению
Сравните Iterator
и ListIterator
.
ListIterator
расширяет интерфейсIterator
ListIterator
может быть использован только для перебора элементов коллекцииList
;Iterator
позволяет перебирать элементы только в одном направлении, при помощи методаnext()
. Тогда какListIterator
позволяет перебирать список в обоих направлениях, при помощи методовnext()
иprevious()
;ListIterator
не указывает на конкретный элемент: его текущая позиция располагается между элементами, которые возвращают методыprevious()
иnext()
.- При помощи
ListIterator
вы можете модифицировать список, добавляя/удаляя элементы с помощью методовadd()
иremove()
.Iterator
не поддерживает данного функционала.
к оглавлению
Что произойдет при вызове Iterator.next()
без предварительного вызова Iterator.hasNext()
?
Если итератор указывает на последний элемент коллекции, то возникнет исключение NoSuchElementException
, иначе будет возвращен следующий элемент.
к оглавлению
Сколько элементов будет пропущено, если Iterator.next()
будет вызван после 10 вызовов Iterator.hasNext()
?
Нисколько — hasNext()
осуществляет только проверку наличия следующего элемента.
к оглавлению
Как поведёт себя коллекция, если вызвать iterator.remove()
?
Если вызову iterator.remove()
предшествовал вызов iterator.next()
, то iterator.remove()
удалит элемент коллекции, на который указывает итератор, в противном случае будет выброшено IllegalStateException()
.
к оглавлению
Как поведёт себя уже инстанциированный итератор для collection
, если вызвать collection.remove()
?
При следующем вызове методов итератора будет выброшено ConcurrentModificationException
.
к оглавлению
Как избежать ConcurrentModificationException
во время перебора коллекции?
- Попробовать подобрать другой итератор, работающий по принципу fail-safe. К примеру, для
List
можно использоватьListIterator
. - Использовать
ConcurrentHashMap
иCopyOnWriteArrayList
. - Преобразовать список в массив и перебирать массив.
- Блокировать изменения списка на время перебора с помощью блока
synchronized
.
Отрицательная сторона последних двух вариантов — ухудшение производительности.
к оглавлению
Какая коллекция реализует дисциплину обслуживания FIFO?
FIFO, First-In-First-Out («первым пришел-первым ушел») — по этому принципу построена коллекция Queue
.
к оглавлению
Какая коллекция реализует дисциплину обслуживания FILO?
FILO, First-In-Last-Out («первым пришел, последним ушел») — по этому принципу построена коллекция Stack
.
к оглавлению
Чем отличается ArrayList
от Vector
?
Зачем добавили ArrayList
, если уже был Vector
?
- Методы класса
Vector
синхронизированы, аArrayList
— нет; - По умолчанию,
Vector
удваивает свой размер, когда заканчивается выделенная под элементы память.ArrayList
же увеличивает свой размер только на половину.
Vector
это устаревший класс и его использование не рекомендовано.
к оглавлению
Чем отличается ArrayList
от LinkedList
? В каких случаях лучше использовать первый, а в каких второй?
ArrayList
это список, реализованный на основе массива, а LinkedList
— это классический двусвязный список, основанный на объектах с ссылками между ними.
ArrayList
:
- доступ к произвольному элементу по индексу за константное время O(1);
- доступ к элементам по значению за линейное время O(N);
- вставка в конец в среднем производится за константное время O(1);
- удаление произвольного элемента из списка занимает значительное время т.к. при этом все элементы, находящиеся «правее» смещаются на одну ячейку влево (реальный размер массива (capacity) не изменяется);
- вставка элемента в произвольное место списка занимает значительное время т.к. при этом все элементы, находящиеся «правее» смещаются на одну ячейку вправо;
- минимум накладных расходов при хранении.
LinkedList
:
- на получение элемента по индексу или значению потребуется линейное время O(N);
- на добавление и удаление в начало или конец списка потребуется константное O(1);
- вставка или удаление в/из произвольного место константное O(1);
- требует больше памяти для хранения такого же количества элементов, потому что кроме самого элемента хранятся еще указатели на следующий и предыдущий элементы списка.
В целом, LinkedList
в абсолютных величинах проигрывает ArrayList
и по потребляемой памяти, и по скорости выполнения операций. LinkedList
предпочтительно применять, когда нужны частые операции вставки/удаления или в случаях, когда необходимо гарантированное время добавления элемента в список.
к оглавлению
Что работает быстрее ArrayList
или LinkedList
?
Смотря какие действия будут выполняться над структурой.
к оглавлению
Какое худшее время работы метода contains()
для элемента, который есть в LinkedList
?
O(N). Время поиска элемента линейно пропорционально количеству элементов в списке.
к оглавлению
Какое худшее время работы метода contains()
для элемента, который есть в ArrayList
?
O(N). Время поиска элемента линейно пропорционально количеству элементов в списке.
к оглавлению
Какое худшее время работы метода add()
для LinkedList
?
O(N). Добавление в начало/конец списка осуществляется за время O(1).
к оглавлению
Какое худшее время работы метода add()
для ArrayList
?
O(N). Вставка элемента в конец списка осуществляется за время O(1), но если вместимость массива недостаточна, то происходит создание нового массива с увеличенным размером и копирование всех элементов из старого массива в новый.
к оглавлению
Необходимо добавить 1 млн. элементов, какую структуру вы используете?
Однозначный ответ можно дать только исходя из информации о том в какую часть списка происходит добавление элементов, что потом будет происходить с элементами списка, существуют ли какие-то ограничения по памяти или скорости выполнения.
к оглавлению
Как происходит удаление элементов из ArrayList
? Как меняется в этом случае размер ArrayList
?
При удалении произвольного элемента из списка, все элементы, находящиеся «правее» смещаются на одну ячейку влево и реальный размер массива (его емкость, capacity) не изменяется никак. Механизм автоматического «расширения» массива существует, а вот автоматического «сжатия» нет, можно только явно выполнить «сжатие» командой trimToSize()
.
к оглавлению
Предложите эффективный алгоритм удаления нескольких рядом стоящих элементов из середины списка, реализуемого ArrayList
.
Допустим нужно удалить n
элементов с позиции m
в списке. Вместо выполнения удаления одного элемента n
раз (каждый раз смещая на 1 позицию элементы, стоящие «правее» в списке), нужно выполнить смещение всех элементов, стоящих «правее» n + m
позиции на n
элементов «левее» к началу списка. Таким образом, вместо выполнения n
итераций перемещения элементов списка, все выполняется за 1 проход. Но если говорить об общей эффективности — то самый быстрый способ будет с использованием System.arraycopy()
, и получить к нему доступ можно через метод — subList(int fromIndex, int toIndex)
.
Пример:
import java.io.*;
import java.util.ArrayList;
public class Main {
//позиция с которой удаляем
private static int m = 0;
//количество удаляемых элементов
private static int n = 0;
//количество элементов в списке
private static final int size = 1000000;
//основной список (для удаления вызовом remove() и его копия для удаления путём перезаписи)
private static ArrayList<Integer> initList, copyList;
public static void main(String[] args){
initList = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
initList.add(i);
}
System.out.println("Список из 1.000.000 элементов заполнен");
copyList = new ArrayList<>(initList);
System.out.println("Создана копия спискаn");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try{
System.out.print("С какой позиции удаляем? > ");
m = Integer.parseInt(br.readLine());
System.out.print("Сколько удаляем? > ");
n = Integer.parseInt(br.readLine());
} catch(IOException e){
System.err.println(e.toString());
}
System.out.println("Выполняем удаление вызовом remove()...");
long start = System.currentTimeMillis();
for (int i = m - 1; i < m + n - 1; i++) {
initList.remove(i);
}
long finish = System.currentTimeMillis() - start;
System.out.println("Время удаления с помощью вызова remove(): " + finish);
System.out.println("Размер исходного списка после удаления: " + initList.size());
System.out.println("nВыполняем удаление путем перезаписи...n");
start = System.currentTimeMillis();
removeEfficiently();
finish = System.currentTimeMillis() - start;
System.out.println("Время удаления путём смещения: " + finish);
System.out.println("Размер копии списка:" + copyList.size());
System.out.println("nВыполняем удаление через SubList...n");
start = System.currentTimeMillis();
initList.subList(m - 1, m + n).clear();
finish = System.currentTimeMillis() - start;
System.out.println("Время удаления через саблист: " + finish);
System.out.println("Размер копии списка:" + copyList.size());
}
private static void removeEfficiently(){
/* если необходимо удалить все элементы, начиная с указанного,
* то удаляем элементы с конца до m
*/
if (m + n >= size){
int i = size - 1;
while (i != m - 1){
copyList.remove(i);
i--;
}
} else{
//переменная k необходима для отсчёта сдвига начиная от места вставка m
for (int i = m + n, k = 0; i < size; i++, k++) {
copyList.set(m + k, copyList.get(i));
}
/* удаляем ненужные элементы в конце списка
* удаляется всегда последний элемент, так как время этого действия
* фиксировано и не зависит от размера списка
*/
int i = size - 1;
while (i != size - n - 1){
copyList.remove(i);
i--;
}
//сокращаем длину списка путём удаления пустых ячеек
copyList.trimToSize();
}
}
}
Результат выполнения:
run:
Список из 1.000.000 элементов заполнен
Создана копия списка
С какой позиции удаляем? > 600000
Сколько удаляем? > 20000
Выполняем удаление вызовом remove()...
Время удаления с помощью вызова remove(): 928
Размер исходного списка после удаления: 980000
Выполняем удаление путем перезаписи...
Время удаления путём смещения: 17
Размер копии списка:980000
Выполняем удаление через SubList...
Время удаления через саблист: 1
Размер копии списка:980000
СБОРКА УСПЕШНО ЗАВЕРШЕНА (общее время: 33 секунды)
к оглавлению
Сколько необходимо дополнительной памяти при вызове ArrayList.add()
?
Если в массиве достаточно места для размещения нового элемента, то дополнительной памяти не требуется. Иначе происходит создание нового массива размером в 1,5 раза превышающим существующий (это верно для JDK выше 1.7, в более ранних версиях размер увеличения иной).
к оглавлению
Сколько выделяется дополнительно памяти при вызове LinkedList.add()
?
Создается один новый экземпляр вложенного класса Node
.
к оглавлению
Оцените количество памяти на хранение одного примитива типа byte
в LinkedList
?
Каждый элемент LinkedList
хранит ссылку на предыдущий элемент, следующий элемент и ссылку на данные.
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
//...
}
Для 32-битных систем каждая ссылка занимает 32 бита (4 байта). Сам объект (заголовок) вложенного класса Node
занимает 8 байт. 4 + 4 + 4 + 8 = 20 байт, а т.к. размер каждого объекта в Java кратен 8, соответственно получаем 24 байта. Примитив типа byte
занимает 1 байт памяти, но в JCF примитивы упаковываются: объект типа Byte
занимает в памяти 16 байт (8 байт на заголовок объекта, 1 байт на поле типа byte
и 7 байт для кратности 8). Также напомню, что значения от -128 до 127 кэшируются и для них новые объекты каждый раз не создаются. Таким образом, в x32 JVM 24 байта тратятся на хранение одного элемента в списке и 16 байт — на хранение упакованного объекта типа Byte
. Итого 40 байт.
Для 64-битной JVM каждая ссылка занимает 64 бита (8 байт), размер заголовка каждого объекта составляет 16 байт (два машинных слова). Вычисления аналогичны: 8 + 8 + 8 + 16 = 40 байт и 24 байта. Итого 64 байта.
к оглавлению
Оцените количество памяти на хранение одного примитива типа byte
в ArrayList
?
ArrayList
основан на массиве, для примитивных типов данных осуществляется автоматическая упаковка значения, поэтому 16 байт тратится на хранение упакованного объекта и 4 байта (8 для x64) — на хранение ссылки на этот объект в самой структуре данных. Таким образом, в x32 JVM 4 байта используются на хранение одного элемента и 16 байт — на хранение упакованного объекта типа Byte
. Для x64 — 8 байт и 24 байта соответственно.
к оглавлению
Для ArrayList
или для LinkedList
операция добавления элемента в середину (list.add(list.size()/2, newElement)
) медленнее?
Для ArrayList
:
- проверка массива на вместимость. Если вместимости недостаточно, то увеличение размера массива и копирование всех элементов в новый массив (O(N));
- копирование всех элементов, расположенных правее от позиции вставки, на одну позицию вправо (O(N));
- вставка элемента (O(1)).
Для LinkedList
:
- поиск позиции вставки (O(N));
- вставка элемента (O(1)).
В худшем случае вставка в середину списка эффективнее для LinkedList
. В остальных — скорее всего, для ArrayList
, поскольку копирование элементов осуществляется за счет вызова быстрого системного метода System.arraycopy()
.
к оглавлению
В реализации класса ArrayList
есть следующие поля: Object[] elementData
, int size
. Объясните, зачем хранить отдельно size
, если всегда можно взять elementData.length
?
Размер массива elementData
представляет собой вместимость (capacity) ArrayList
, которая всегда больше переменной size
— реального количества хранимых элементов. При необходимости вместимость автоматически возрастает.
к оглавлению
Сравните интерфейсы Queue
и Deque
.
Кто кого расширяет: Queue
расширяет Deque
, или Deque
расширяет Queue
?
Queue
— это очередь, которая обычно (но необязательно) строится по принципу FIFO (First-In-First-Out) — соответственно извлечение элемента осуществляется с начала очереди, вставка элемента — в конец очереди. Хотя этот принцип нарушает, к примеру, PriorityQueue
, использующая «natural ordering» или переданный Comparator
при вставке нового элемента.
Deque
(Double Ended Queue) расширяет Queue
и согласно документации, это линейная коллекция, поддерживающая вставку/извлечение элементов с обоих концов. Помимо этого реализации интерфейса Deque
могут строиться по принципу FIFO, либо LIFO.
Реализации и Deque
, и Queue
обычно не переопределяют методы equals()
и hashCode()
, вместо этого используются унаследованные методы класса Object, основанные на сравнении ссылок.
к оглавлению
Почему LinkedList
реализует и List
, и Deque
?
LinkedList
позволяет добавлять элементы в начало и конец списка за константное время, что хорошо согласуется с поведением интерфейса Deque
.
к оглавлению
LinkedList
— это односвязный, двусвязный или четырехсвязный список?
Двусвязный
: каждый элемент LinkedList
хранит ссылку на предыдущий и следующий элементы.
к оглавлению
Как перебрать элементы LinkedList
в обратном порядке, не используя медленный get(index)
?
Для этого в LinkedList
есть обратный итератор, который можно получить вызова метод descendingIterator()
.
к оглавлению
Что позволяет сделать PriorityQueue
?
Особенностью PriorityQueue
является возможность управления порядком элементов. По умолчанию, элементы сортируются с использованием «natural ordering», но это поведение может быть переопределено при помощи объекта Comparator
, который задаётся при создании очереди. Данная коллекция не поддерживает null в качестве элементов.
Используя PriorityQueue
, можно, например, реализовать алгоритм Дейкстры для поиска кратчайшего пути от одной вершины графа к другой. Либо для хранения объектов согласно определённого свойства.
к оглавлению
Stack
считается «устаревшим». Чем его рекомендуют заменять? Почему?
Stack
был добавлен в Java 1.0 как реализация стека LIFO (last-in-first-out) и является расширением коллекции Vector
, хотя это несколько нарушает понятие стека (например, класс Vector
предоставляет возможность обращаться к любому элементу по индексу). Является частично синхронизированной коллекцией (кроме метода добавления push()
) с вытекающими отсюда последствиями в виде негативного воздействия на производительность. После добавления в Java 1.6 интерфейса Deque
, рекомендуется использовать реализации именно этого интерфейса, например, ArrayDeque
.
к оглавлению
Зачем нужен HashMap
, если есть Hashtable
?
- Методы класса
Hashtable
синхронизированы, что приводит к снижению производительности, аHashMap
— нет; HashTable
не может содержать элементыnull
, тогда какHashMap
может содержать один ключnull
и любое количество значенийnull
;- Iterator у
HashMap
, в отличие от Enumeration уHashTable
, работает по принципу «fail-fast» (выдает исключение при любой несогласованности данных).
Hashtable
это устаревший класс и его использование не рекомендовано.
к оглавлению
В чем разница между HashMap
и IdentityHashMap
? Для чего нужна IdentityHashMap
?
IdentityHashMap
— это структура данных, так же реализующая интерфейс Map
и использующая при сравнении ключей (значений) сравнение ссылок, а не
вызов метода equals()
. Другими словами, в IdentityHashMap
два ключа k1
и k2
будут считаться равными, если они указывают на один объект, т.е.
выполняется условие k1
== k2
.
IdentityHashMap
не использует метод hashCode()
, вместо которого применяется метод System.identityHashCode()
, по этой причине IdentityHashMap
по сравнению с HashMap
имеет более высокую производительность, особенно если последний хранит объекты с дорогостоящими методами equals()
и hashCode()
.
Одним из основных требований к использованию HashMap
является неизменяемость ключа, а, т.к. IdentityHashMap
не использует методы equals()
и hashCode()
, то это правило на него не распространяется.
IdentityHashMap
может применяться для реализации сериализации/клонирования. При выполнении подобных алгоритмов программе необходимо обслуживать хэш-таблицу со всеми ссылками на объекты, которые уже были обработаны. Такая структура не должна рассматривать уникальные объекты как равные, даже если метод equals()
возвращает true
.
Пример кода:
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
public class Q2 {
public static void main(String[] args) {
Q2 q = new Q2();
q.testHashMapAndIdentityHashMap();
}
private void testHashMapAndIdentityHashMap() {
CreditCard visa = new CreditCard("VISA", "04/12/2019");
Map<CreditCard, String> cardToExpiry = new HashMap<>();
Map<CreditCard, String> cardToExpiryIdenity = new IdentityHashMap<>();
System.out.println("adding to HM");
// inserting objects to HashMap
cardToExpiry.put(visa, visa.getExpiryDate());
// inserting objects to IdentityHashMap
cardToExpiryIdenity.put(visa, visa.getExpiryDate());
System.out.println("adding to IHM");
System.out.println("before modifying keys");
String result = cardToExpiry.get(visa) != null ? "Yes" : "No";
System.out.println("Does VISA card exists in HashMap? " + result);
result = cardToExpiryIdenity.get(visa) != null ? "Yes" : "No";
System.out.println("Does VISA card exists in IdenityHashMap? " + result);
// modifying value object
visa.setExpiryDate("02/11/2030");
System.out.println("after modifying keys");
result = cardToExpiry.get(visa) != null ? "Yes" : "No";
System.out.println("Does VISA card exists in HashMap? " + result);
result = cardToExpiryIdenity.get(visa) != null ? "Yes" : "No";
System.out.println("Does VISA card exists in IdenityHashMap? " + result);
System.out.println("cardToExpiry.containsKey");
System.out.println(cardToExpiry.containsKey(visa));
System.out.println("cardToExpiryIdenity.containsKey");
System.out.println(cardToExpiryIdenity.containsKey(visa));
}
}
class CreditCard {
private String issuer;
private String expiryDate;
public CreditCard(String issuer, String expiryDate) {
this.issuer = issuer;
this.expiryDate = expiryDate;
}
public String getIssuer() {
return issuer;
}
public String getExpiryDate() {
return expiryDate;
}
public void setExpiryDate(String expiry) {
this.expiryDate = expiry;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((expiryDate == null) ? 0 : expiryDate.hashCode());
result = prime * result + ((issuer == null) ? 0 : issuer.hashCode());
System.out.println("hashCode = " + result);
return result;
}
@Override
public boolean equals(Object obj) {
System.out.println("equals !!! ");
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CreditCard other = (CreditCard) obj;
if (expiryDate == null) {
if (other.expiryDate != null)
return false;
} else if (!expiryDate.equals(other.expiryDate))
return false;
if (issuer == null) {
if (other.issuer != null)
return false;
} else if (!issuer.equals(other.issuer))
return false;
return true;
}
}
Результат выполнения кода:
adding to HM
hashCode = 1285631513
adding to IHM
before modifying keys
hashCode = 1285631513
Does VISA card exists in HashMap? Yes
Does VISA card exists in IdenityHashMap? Yes
after modifying keys
hashCode = 791156485
Does VISA card exists in HashMap? No
Does VISA card exists in IdenityHashMap? Yes
cardToExpiry.containsKey
hashCode = 791156485
false
cardToExpiryIdenity.containsKey
true
к оглавлению
В чем разница между HashMap
и WeakHashMap
? Для чего используется WeakHashMap
?
В Java существует 4 типа ссылок: сильные (strong reference), мягкие (SoftReference), слабые (WeakReference) и фантомные (PhantomReference).
Особенности каждого типа ссылок связаны с работой Garbage Collector. Если объект можно достичь только с помощью цепочки WeakReference (то есть на него
отсутствуют сильные и мягкие ссылки), то данный объект будет помечен на удаление.
WeakHashMap
— это структура данных, реализующая интерфейс Map
и основанная на использовании WeakReference для хранения ключей. Таким образом, пара
«ключ-значение» будет удалена из WeakHashMap
, если на объект-ключ более не имеется сильных ссылок.
В качестве примера использования такой структуры данных можно привести следующую ситуацию: допустим имеются объекты, которые необходимо расширить
дополнительной информацией, при этом изменение класса этих объектов нежелательно либо невозможно. В этом случае добавляем каждый объект
в WeakHashMap
в качестве ключа, а в качестве значения — нужную информацию. Таким образом, пока на объект имеется сильная ссылка (либо мягкая), можно
проверять хэш-таблицу и извлекать информацию. Как только объект будет удален, то WeakReference для этого ключа будет помещен в ReferenceQueue и затем
соответствующая запись для этой слабой ссылки будет удалена из WeakHashMap
.
к оглавлению
В WeakHashMap
используются WeakReferences. А почему бы не создать SoftHashMap
на SoftReferences?
SoftHashMap
представлена в сторонних библиотеках, например, в Apache Commons
.
к оглавлению
В WeakHashMap
используются WeakReferences. А почему бы не создать PhantomHashMap
на PhantomReferences?
PhantomReference при вызове метода get()
возвращает всегда null
, поэтому тяжело представить назначение такой структуры данных.
к оглавлению
LinkedHashMap
— что в нем от LinkedList
, а что от HashMap
?
Реализация LinkedHashMap
отличается от HashMap
поддержкой двухсвязанного списка, определяющего порядок итерации по элементам структуры данных. По умолчанию элементы списка упорядочены согласно их порядку добавления в LinkedHashMap
(insertion-order). Однако порядок итерации можно изменить, установив параметр конструктора accessOrder
в значение true
. В этом случае доступ осуществляется по порядку последнего обращения к элементу (access-order). Это означает, что при вызове методов get()
или put()
элемент, к которому обращаемся, перемещается в конец списка.
При добавлении элемента, который уже присутствует в LinkedHashMap
(т.е. с одинаковым ключом), порядок итерации по элементам не изменяется.
к оглавлению
В чем проявляется «сортированность» SortedMap
, кроме того, что toString()
выводит все элементы по порядку?
Так же оно проявляется при итерации по коллекции.
к оглавлению
Как устроен HashMap
?
HashMap
состоит из «корзин» (bucket). С технической точки зрения «корзины» — это элементы массива, которые хранят ссылки на списки элементов. При добавлении новой пары «ключ-значение», вычисляет хэш-код ключа, на основании которого вычисляется номер корзины (номер ячейки массива), в которую попадет новый элемент. Если корзина пустая, то в нее сохраняется ссылка на вновь добавляемый элемент, если же там уже есть элемент, то происходит последовательный переход по ссылкам между элементами в цепочке, в поисках последнего элемента, от которого и ставится ссылка на вновь добавленный элемент. Если в списке был найден элемент с таким же ключом, то он заменяется.
к оглавлению
Согласно Кнуту и Кормену существует две основных реализации хэш-таблицы: на основе открытой адресации и на основе метода цепочек. Как реализована HashMap
? Почему, по вашему мнению, была выбрана именно эта реализация? В чем плюсы и минусы каждого подхода?
HashMap
реализован с использованием метода цепочек, т.е. каждой ячейке массива (корзине) соответствует свой связный список и при возникновении коллизии осуществляется добавление нового элемента в этот список.
Для метода цепочек коэффициент заполнения может быть больше 1 и с увеличением числа элементов производительность убывает линейно. Такие таблицы удобно использовать, если заранее неизвестно количество хранимых элементов, либо их может быть достаточно много, что приводит к большим значениям коэффициента заполнения.
Среди методов открытой реализации различают:
- линейное пробирование;
- квадратичное пробирование;
- двойное хэширование.
Недостатки структур с методом открытой адресации:
- Количество элементов в хэш-таблице не может превышать размера массива. По мере увеличения числа элементов и повышения коэффициента заполнения производительность структуры резко падает, поэтому необходимо проводить перехэширование.
- Сложно организовать удаление элемента.
- Первые два метода открытой адресации приводят к проблеме первичной и вторичной группировок.
Преимущества хэш-таблицы с открытой адресацией:
- отсутствие затрат на создание и хранение объектов списка;
- простота организации сериализации/десериализации объекта.
к оглавлению
Как работает HashMap
при попытке сохранить в него два элемента по ключам с одинаковым hashCode()
, но для которых equals() == false
?
По значению hashCode()
вычисляется индекс ячейки массива, в список которой этот элемент будет добавлен. Перед добавлением осуществляется проверка на наличие элементов в этой ячейке. Если элементы с таким hashCode()
уже присутствует, но их equals()
методы не равны, то элемент будет добавлен в конец списка.
к оглавлению
Какое начальное количество корзин в HashMap
?
В конструкторе по умолчанию — 16, используя конструкторы с параметрами можно задавать произвольное начальное количество корзин.
к оглавлению
Какова оценка временной сложности операций над элементами из HashMap
? Гарантирует ли HashMap
указанную сложность выборки элемента?
В общем случае операции добавления, поиска и удаления элементов занимают константное время.
Данная сложность не гарантируется, т.к. если хэш-функция распределяет элементы по корзинам равномерно, временная сложность станет не хуже Логарифмического времени O(log(N)), а в случае, когда хэш-функция постоянно возвращает одно и то же значение, HashMap
превратится в связный список со сложностью О(n).
Пример кода двоичного поиска:
public class Q {
public static void main(String[] args) {
Q q = new Q();
q.binSearch();
}
private void binSearch() {
int[] inpArr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
Integer result = binSearchF(inpArr, 1, 0, inpArr.length - 1);
System.out.println("-----------------------");
result = binSearchF(inpArr, 2, 0, inpArr.length - 1);
System.out.println("Found at position " + result);
}
private Integer binSearchF(int[] inpArr, int searchValue, int low, int high) {
Integer index = null;
while (low <= high) {
System.out.println("New iteration, low = " + low + ", high = " + high);
int mid = (low + high) / 2;
System.out.println("trying mid = " + mid + " inpArr[mid] = " + inpArr[mid]);
if (inpArr[mid] < searchValue) {
low = mid + 1;
System.out.println("inpArr[mid] (" + inpArr[mid] + ") < searchValue(" + searchValue + "), mid = " + mid
+ ", setting low = " + low);
} else if (inpArr[mid] > searchValue) {
high = mid - 1;
System.out.println("inpArr[mid] (" + inpArr[mid] + ") > searchValue(" + searchValue + "), mid = " + mid
+ ", setting high = " + high);
} else if (inpArr[mid] == searchValue) {
index = mid;
System.out.println("found at index " + mid);
break;
}
}
return index;
}
}
к оглавлению
Возможна ли ситуация, когда HashMap
выродится в список даже с ключами имеющими разные hashCode()
?
Это возможно в случае, если метод, определяющий номер корзины будет возвращать одинаковые значения.
к оглавлению
В каком случае может быть потерян элемент в HashMap
?
Допустим, в качестве ключа используется не примитив, а объект с несколькими полями. После добавления элемента в HashMap
у объекта, который выступает в качестве ключа, изменяют одно поле, которое участвует в вычислении хэш-кода. В результате при попытке найти данный элемент по исходному ключу, будет происходить обращение к правильной корзине, а вот equals
уже не найдет указанный ключ в списке элементов. Тем не менее, даже если equals
реализован таким образом, что изменение данного поля объекта не влияет на результат, то после увеличения размера корзин и пересчета хэш-кодов элементов, указанный элемент, с измененным значением поля, с большой долей вероятности попадет в совершенно другую корзину и тогда уже потеряется совсем.
к оглавлению
Почему нельзя использовать byte[]
в качестве ключа в HashMap
?
Хэш-код массива не зависит от хранимых в нем элементов, а присваивается при создании массива (метод вычисления хэш-кода массива не переопределен и вычисляется по стандартному Object.hashCode()
на основании адреса массива). Так же у массивов не переопределен equals
и выполняется сравнение указателей. Это приводит к тому, что обратиться к сохраненному с ключом-массивом элементу не получится при использовании другого массива такого же размера и с такими же элементами, доступ можно осуществить лишь в одном случае — при использовании той же самой ссылки на массив, что использовалась для сохранения элемента.
к оглавлению
Какова роль equals()
и hashCode()
в HashMap
?
hashCode
позволяет определить корзину для поиска элемента, а equals
используется для сравнения ключей элементов в списке корзины и искомого ключа.
к оглавлению
Каково максимальное число значений hashCode()
?
Число значений следует из сигнатуры int hashCode()
и равно диапазону типа int
— 232.
к оглавлению
Какое худшее время работы метода get(key) для ключа, которого нет в HashMap
?
Какое худшее время работы метода get(key) для ключа, который есть в HashMap
?
O(N). Худший случай — это поиск ключа в HashMap
, вырожденного в список по причине совпадения ключей по hashCode()
и для выяснения хранится ли элемент с определённым ключом может потребоваться перебор всего списка.
к оглавлению
Сколько переходов происходит в момент вызова HashMap.get(key)
по ключу, который есть в таблице?
- ключ равен
null
: 1 — выполняется единственный методgetForNullKey()
. - любой ключ отличный от
null
: 4 — вычисление хэш-кода ключа; определение номера корзины; поиск значения; возврат значения.
к оглавлению
Сколько создается новых объектов, когда вы добавляете новый элемент в HashMap
?
Один новый объект статического вложенного класса Entry<K,V>
.
к оглавлению
Как и когда происходит увеличение количества корзин в HashMap
?
Помимо capacity
у HashMap
есть еще поле loadFactor
, на основании которого, вычисляется предельное количество занятых корзин capacity * loadFactor
. По умолчанию loadFactor = 0.75
. По достижению предельного значения, число корзин увеличивается в 2 раза и для всех хранимых элементов вычисляется новое «местоположение» с учетом нового числа корзин.
к оглавлению
Объясните смысл параметров в конструкторе HashMap(int initialCapacity, float loadFactor)
.
initialCapacity
— исходный размерHashMap
, количество корзин в хэш-таблице в момент её создания.loadFactor
— коэффициент заполненияHashMap
, при превышении которого происходит увеличение количества корзин и автоматическое перехэширование. Равен отношению числа уже хранимых элементов в таблице к её размеру.
к оглавлению
Будет ли работать HashMap
, если все добавляемые ключи будут иметь одинаковый hashCode()
?
Да, будет, но в этом случае HashMap
вырождается в связный список и теряет свои преимущества.
к оглавлению
Как перебрать все ключи Map
?
Использовать метод keySet()
, который возвращает множество Set<K>
ключей.
к оглавлению
Как перебрать все значения Map
?
Использовать метод values()
, который возвращает коллекцию Collection<V>
значений.
к оглавлению
Как перебрать все пары «ключ-значение» в Map
?
Использовать метод entrySet()
, который возвращает множество Set<Map.Entry<K, V>
пар «ключ-значение».
к оглавлению
В чем отличия TreeSet
и HashSet
?
TreeSet
обеспечивает упорядоченно хранение элементов в виде красно-черного дерева. Сложность выполнения основных операций не хуже O(log(N)) (_
Логарифмическое время_).
HashSet
использует для хранения элементов такой же подход, что и HashMap
, за тем отличием, что в HashSet
в качестве ключа и значения выступает
сам элемент
, кроме того HashSet
не поддерживает упорядоченное хранение элементов и обеспечивает временную сложность выполнения операций
аналогично HashMap
.
к оглавлению
Что будет, если добавлять элементы в TreeSet
по возрастанию?
В основе TreeSet
лежит красно-черное дерево, которое умеет само себя балансировать. В итоге, TreeSet
все равно в каком порядке вы добавляете в него элементы, преимущества этой структуры данных будут сохраняться.
к оглавлению
Чем LinkedHashSet
отличается от HashSet
?
LinkedHashSet
отличается от HashSet
только тем, что в его основе лежит LinkedHashMap
вместо HashMap
. Благодаря этому порядок элементов при обходе коллекции является идентичным порядку добавления элементов (insertion-order). При добавлении элемента, который уже присутствует в LinkedHashSet
(т.е. с одинаковым ключом), порядок обхода элементов не изменяется.
к оглавлению
Для Enum
есть специальный класс java.util.EnumSet
. Зачем? Чем авторов не устраивал HashSet
или TreeSet
?
EnumSet
— это реализация интерфейса Set
для использования с перечислениями (Enum
). В структуре данных хранятся объекты только одного типа Enum
, указываемого при создании. Для хранения значений EnumSet
использует массив битов (bit vector), — это позволяет получить высокую компактность и эффективность. Проход по EnumSet
осуществляется согласно порядку объявления элементов перечисления.
Все основные операции выполняются за O(1) и обычно (но негарантированно) быстрей аналогов из HashSet
, а пакетные операции (bulk operations), такие как containsAll()
и retainAll()
выполняются даже горазда быстрей.
Помимо всего EnumSet
предоставляет множество статических методов инициализации для упрощенного и удобного создания экземпляров.
к оглавлению
Какие существуют способы перебирать элементы списка?
- Цикл с итератором
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
//iterator.next();
}
- Цикл
for
for (int i = 0; i < list.size(); i++) {
//list.get(i);
}
- Цикл
while
int i = 0;
while (i < list.size()) {
//list.get(i);
i++;
}
- «for-each»
for (String element : list) {
//element;
}
к оглавлению
Каким образом можно получить синхронизированные объекты стандартных коллекций?
С помощью статических методов synchronizedMap()
и synchronizedList()
класса Collections
. Данные методы возвращают синхронизированный декоратор переданной коллекции. При этом все равно в случае обхода по коллекции требуется ручная синхронизация.
Map m = Collections.synchronizedMap(new HashMap());
List l = Collections.synchronizedList(new ArrayList());
Начиная с Java 6 JCF был расширен специальными коллекциями, поддерживающими многопоточный доступ, такими как CopyOnWriteArrayList
и ConcurrentHashMap
.
к оглавлению
Как получить коллекцию только для чтения?
При помощи:
Collections.unmodifiableList(list)
;Collections.unmodifiableSet(set)
;Collections.unmodifiableMap(map)
.
Эти методы принимают коллекцию в качестве параметра, и возвращают коллекцию только для чтения с теми же элементами внутри.
к оглавлению
Напишите однопоточную программу, которая заставляет коллекцию выбросить ConcurrentModificationException
.
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
for (Integer integer : list) {
list.remove(1);
}
}
к оглавлению
Приведите пример, когда какая-либо коллекция выбрасывает UnsupportedOperationException
.
public static void main(String[] args) {
List<Integer> list = Collections.emptyList();
list.add(0);
}
к оглавлению
Реализуйте симметрическую разность двух коллекций используя методы Collection
(addAll(...)
, removeAll(...)
, retainAll(...)
).
Симметрическая разность двух коллекций — это множество элементов, одновременно не принадлежащих обоим исходным коллекциям.
<T> Collection<T> symmetricDifference(Collection<T> a, Collection<T> b) {
// Объединяем коллекции.
Collection<T> result = new ArrayList<>(a);
result.addAll(b);
// Получаем пересечение коллекций.
Collection<T> intersection = new ArrayList<>(a);
intersection.retainAll(b);
// Удаляем элементы, расположенные в обоих коллекциях.
result.removeAll(intersection);
return result;
}
к оглавлению
Как, используя LinkedHashMap, сделать кэш c «invalidation policy»?
Необходимо использовать LRU-алгоритм (Least Recently Used algorithm) и LinkedHashMap
с access-order. В этом случае при обращении к элементу он будет перемещаться в конец списка, а наименее используемые элементы будут постепенно группироваться в начале списка. Так же в стандартной реализации LinkedHashMap
есть метод removeEldestEntries()
, который возвращает true
, если текущий объект LinkedHashMap
должен удалить наименее используемый элемент из коллекции при использовании методов put()
и putAll()
.
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private static final int MAX_ENTRIES = 10;
public LRUCache(int initialCapacity) {
super(initialCapacity, 0.85f, true);
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > MAX_ENTRIES;
}
}
Стоит заметить, что LinkedHashMap
не позволяет полностью реализовать LRU-алгоритм, поскольку при вставке уже имеющегося в коллекции элемента порядок итерации по элементам не меняется.
к оглавлению
Как одной строчкой скопировать элементы любой collection
в массив?
Object[] array = collection.toArray();
к оглавлению
Как одним вызовом из List
получить List
со всеми элементами, кроме первых и последних 3-х?
List<Integer> subList = list.subList(3, list.size() - 3);
к оглавлению
Как одной строчкой преобразовать HashSet
в ArrayList
?
ArrayList<Integer> list = new ArrayList<>(new HashSet<>());
к оглавлению
Как одной строчкой преобразовать ArrayList
в HashSet
?
HashSet<Integer> set = new HashSet<>(new ArrayList<>());
к оглавлению
Сделайте HashSet
из ключей HashMap
.
HashSet<Object> set = new HashSet<>(map.keySet());
к оглавлению
Сделайте HashMap
из HashSet<Map.Entry<K, V>>
.
HashMap<K, V> map = new HashMap<>(set.size());
for (Map.Entry<K, V> entry : set) {
map.put(entry.getKey(), entry.getValue());
}
к оглавлению
Источник
- parshinpn.pro
- Хабрахабр
- Quizful
- JavaRush
- Хабрахабр:Справочник по Java Collections Framework
Вопросы для собеседования
Продолжение ответов на вопросы.
1 часть.
3 часть.
Object.equals() + Object.hashCode()
1. Могут ли у разных объектов в памяти (ref0 != ref1)
быть ref0.hashCode() == ref1.hashCode()
?
Да, могут. Метод hashCode() не гарантирует уникальность возвращаемого значения.
2. Могут ли у разных объектов в памяти (ref0 != ref1)
быть ref0.equals(ref1) == true
?
Да, могут. Для этого в классе этих объектов должен быть переопределен метод equals()
.
Если используется метод Object.equals(), то для двух ссылок x
и y
метод вернет true
тогда и только тогда, когда обе ссылки указывают на один и тот же объект (т.е. x
== y
возвращает true
).
3. Могут ли у разных ссылок на один объект в памяти (ref0 == ref1)
быть ref0.equals(ref1) == false
?
Нет, не может. Метод equals() должен гарантировать свойство рефлексивности: для любых ненулевых ссылок x
метод x.equals(x)
должен возвращать true
.
4. Есть класс Point{int x, y;}
. Почему хэш-код в виде 31 * x + y
предпочтительнее чем x + y
?
Множитель создает зависимость значения хэш-кода от очередности обработки полей, а это дает гораздо лучшую хэш-функцию.
5. Если у класса Point{int x, y;}
«правильно» реализовать метод equals (return ref0.x == ref1.x && ref0.y == ref1.y)
, но сделать хэш-код в виде int hashCode() {return x;}
, то будут ли корректно такие точки помещаться и извлекаться из HashSet
?
HashSet
использует HashMap
для хранения элементов (в качестве ключа используется сам объект). При добавлении элемента в HashMap
вычисляется хэшкод и позиция в массиве, куда будет вставлен новый элемент. У всех экземпляров класса Point
одинаковый хэшкод, что приводит в вырождению хэш-таблицы в список. При возникновении коллизии осуществляется проверка на наличие уже такого элемента в текущем списке:
e.hash == hash && ((k = e.key) == key || key.equals(k))
Если элемент найден, то его значение перезаписывается. В нашем случае для разных объектов метод equals() будет возвращать false
. Соответственно новый элемент будет добавлен в HashSet
. Извлечение элемента также будет осуществляться успешно.
Но производительность такого кода будет низкой и преимущества хэш-таблиц использоваться не будут.
6. equals()
порождает отношение эквивалентности. Какими из свойств обладает такое отношение: коммутативность, симметричность, рефлексивность, дистрибутивность, ассоциативность, транзитивность?
Метод equals() должен обеспечивать:
- симметричность (для любых ненулевых ссылок
x
иy
методx.equals(y)
должен возвращатьtrue
тогда и только тогда, когдаy.equals(x)
возвращаетtrue
); - рефлексивность (для любых ненулевых ссылок
x
методx.equals(x)
должен возвращатьtrue
.); - транзитивность (для любых ненулевых ссылок
x
,y
иz
, еслиx.equals(y)
возвращаетtrue
иy.equals(z)
возвращаетtrue
, тогда иx.equals(z)
должен возвращатьtrue
).
Также есть ещё два свойства: постоянство и неравенство null
.
7. Можно ли так реализовать equals(Object that) {return this.hashCode() == that.hashCode()}
?
Строго говоря нельзя, поскольку метод hashCode() не гарантирует уникальность значения для каждого объекта.
Однако для сравнения экземпляров класса Object такой код допустим, т.к. метод hashCode() в классе Object возвращает уникальные значения для разных объектов (вычисления основаны на использовании адреса объекта в памяти).
8. В equals
требуется проверять, что аргумент (equals(Object that))
такого же типа как и сам объект. В чем разница между this.getClass() == that.getClass()
и that instanceof MyClass
?
Оператор instanceof сравнивает объект и указанный тип. Его можно использовать для проверки является ли данный объект экземпляром некоторого класса, либо экземпляром его дочернего класса, либо экземпляром класса, который реализует указанный интерфейс.
getClass() = ...
проверяет два типа на идентичность.
Для корректной реализации контракта метода equals() необходимо использовать точное сравнение с помощью getClass().
9. Можно ли реализовать метод equals
класса MyClass
вот так: class MyClass {public boolean equals(MyClass that) {return this == that;}}
?
Реализовать можно, но данный метод не переопределяет метод equals() класса Object, а перегружает его.
10. Будет ли работать HashMap
, если все ключи будут возвращать int hashCode() {return 42;}
?
Да, будет. Но тогда хэш-таблица вырождается в связный список и теряет свои преимущества.
HashMap, HashSet
1. Зачем добавили HashMap
, если уже был Hashtable
?
Класс Hashtable
был введен в JDK 1.0 и не является частью Java Collection Framework. Методы класса Hashtable
синхронизированы, что обеспечивает потокобезопасность, но это приводит к снижению производительности, поэтому и был введен класс HashMap
, методы которого не синхронизированы.
Помимо этого класс HashMap
обладает некоторыми другими отличиями: например, позволяет хранить один null
ключ и множество null
значений.
2. Согласно Кнуту и Кормену существует две основных реализации хэш-таблицы: на основе открытой адресацией и на основе метода цепочек. Как реализована HashMap
? Почему так сделали (по вашему мнению)? В чем минусы и плюсы каждого подхода?
Класс HashMap
реализован с использованием метода цепочек, т.е. каждой ячейке массива соответствует свой связный список и при возникновении коллизии осуществляется добавление нового элемента в этот список.
Для метода цепочек коэффициент заполнения может быть больше 1, с увеличением числа элементов производительность убывает линейно. Такие таблицы удобно использовать, если заранее неизвестно количество хранимых элементов, либо их может быть достаточно много, что приводит к большим значениям коэффициента заполнения.
Среди методов открытой реализации различают:
- линейное пробирование;
- квадратичное пробирование;
- двойное хеширование.
Основные недостатки структур с методом открытой адресации:
- Количество элементов в таблице не может превышать размера массива. По мере увеличения числа элементов в таблице и повышения коэффициента заполнения (load factor) производительность структуры резко падает, поэтому необходимо проводить перехеширование.
- Сложно организовать удаление элемента.
- Также первые два метода открытой адресации приводят к проблеме первичной и вторичной группировок.
Основное преимущество хэш-таблицы с открытой адресацией — это отсутствие затрат на создание и хранение объектов списка. Также проще организовать сериализацию/десериализацию объекта.
3. Сколько переходов по ссылкам происходит, когда вы делаете HashMap.get(key)
по ключу, который есть в таблице?
Возможно, я неправильно понял этот вопрос. За переходы по ссылке в данном ответе я считаю вызовы методов.
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
Рассмотрим первый случай, когда ключ равен null
: выполняем метод getForNullKey()
.
private V getForNullKey() {
if (size == 0) {
return null;
}
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
В цикле foreach
проходимся по списку значений для ключа и возвращаем нужное значение. Таким образом, получаем 1 переход.
Второй случай: ключ не равен null
. Выполняем метод getEntry(key)
.
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
Вычисляется хэш-код ключа (метод hash(key)
), затем определяется индекс ячейки массива, в которой будем искать значение (метод indexFor(hash, table.length)
).
После того, как нашли нужную пару «ключ-значение» возвращаем значение (метод entry.getValue()
). Таким образом, получаем 4 перехода.
4. Сколько создается новых объектов, когда вы добавляете новый элемент в HashMap
?
Один новый объект статического вложенного класса Entry<K,V>
.
5. Как работает HashMap
при попытке сохранить в нее два элемента по ключам с одинаковым hashCode
, но для которых equals == false
?
По значению hashCode
вычисляется индекс ячейки массива, в список которой будет происходить добавление элемента. Перед добавлением осуществляется проверка на наличие уже элементов в этой ячейке. Если элементов нет, то происходит добавление. Если возникает коллизия, то итеративно осуществляется обход списка в поисках элемента с таким же ключом и хэш-кодом. Если такой элемент найден, то его значение перезаписывается, а старое — возвращается. Поскольку в условии сказано, что добавляемые ключи — разные, то второй элемент будет добавлен в начало списка.
6. HashMap
может выродиться в список даже для ключей с разным hashCode
. Как это возможно?
Это возможно в случае, если метод, определяющий номер ячейки массива по hashCode
будет возвращать одинаковое значение.
7. Какое худшее время работы метода get(key)
для ключа, которого нет в таблице (O(1), O(log(N)), O(N), O(N*log(N)), O(N*N))
?
O(N).
Худший случай — это поиск ключа в таблице, вырожденной в список, перебор ключей которой занимает линейно пропорциональное время количеству хранимых элементов.
8. Какое худшее время работы метода get(key)
для ключа, который есть в таблице (O(1), O(log(N)), O(N), O(N*log(N)), O(N*N))
?
O(N).
Аналогичные рассуждения, что и для предыдущего вопроса.
9. Объясните смысл параметров в конструкторе HashMap(int initialCapacity, float loadFactor)
.
int initialCapacity
— исходный размер HashMap (количество корзин в хэш-таблице в момент её создания).
float loadFactor
— коэффициент заполнения HashMap
. Равен отношению числа хранимых элементов в таблице к её размеру. Является мерой заполнения таблицы элементами, при превышении которой происходит автоматической перехеширование.
10. В чем разница между HashMap
и IdentityHashMap
? Для чего нужна IdentityHashMap
? Как может быть полезна для реализации сериализации или клонирования?
IdentityHashMap — это структура данных, реализующая интерфейс Map, но использующая сравнение ссылок вместо метода equals()
при сравнении ключей (значений). Другими словами, в IdentityHashMap
два ключа k1
и k2
будут рассматриваться равными, если выполняется условие k1 == k2
(в стандартной реализации интерфейса Map
(например, HashMap) ключи k1
и k2
считаются равными, если выполняется условие (k1 == null ? k2 == null : k1.equals(k2))
).
IdentityHashMap
не использует метод hashCode()
, вместо которого применяется метод System.identityHashCode(Object).
Другое отличие (как следствие) заключается в более высокой производительности IdentityHashMap
по сравнению с HashMap
, если последний хранит объекты с дорогостоящими методами equals()
и hashCode()
.
Одним из основных требований к использованию HashMap
является неизменяемость ключа, однако это требование не распространяется на IdentityHashMap
, который не использует методы equals()
и hashCode()
.
Согласно документации, такая структура данных может применяться для реализации сериализации/клонирования. Для выполнения подобных алгоритмов программе необходимо обслуживать таблицу со всеми ссылками на объекты, которые уже были обработаны. Такая таблица не должна рассматривать уникальные объекты как равные, даже если метод equals() возвращает true
.
11. В чем разница между HashMap
и WeakHashMap
? Для чего нужна WeakHashMap
?
Перед рассмотрением WeakHashMap кратко напомню, что такое WeakReference. В Java существует 4 типа ссылок: сильные (strong reference
), мягкие (SoftReference), слабые (WeakReference
) и фантомные (PhantomReference). Особенности каждого типа ссылок связаны с работой Garbage Collector. Если объект можно достичь только с помощью цепочки WeakReference
(то есть на него не ссылаются сильные и мягкие ссылки), то данный объект будет отмечен для удаления. Хорошая статья с подробным описанием каждого типа ссылок — Understanding Weak References.
WeakHashMap
— это структура данных, реализующая интерфейс Map
и основанная на использовании WeakReference
для хранения ключей. Таким образом, пара «ключ-значение» будет удалена из WeakHashMap
, если на объект-ключ более не имеется сильных ссылок.
В качестве примера использования такой структуры данных можно привести следующую ситуацию: допустим имеются объекты, которые необходимо расширить дополнительной информацией, при этом изменение класса этих объектов нежелательно либо невозможно. В этом случае добавляем каждый объект в WeakHashMap
в качестве ключа, а в качестве значения — нужную информацию. Таким образом, пока на объект имеется сильная ссылка (либо мягкая), можно проверять хэш-таблицу и извлекать информацию. Как только объект будет удален, то WeakReference
для этого ключа будет помещен в ReferenceQueue
и затем соответствующая запись для этой слабой ссылки будет удалена из WeakHashMap
.
12. В WeakHashMap
используются WeakReferences
. А почему бы не создать SoftHashMap
на SoftReferences
?
SoftHashMap
представлена в стронних библиотеках, например, в Apache Commons.
13. В WeakHashMap
используются WeakReferences
. А почему бы не создать PhantomHashMap
на PhantomReferences
?
PhantomReference при вызове метода get()
возвращает всегда null
, поэтому, я думаю, создание PhantomHashMap
просто невозможно. Плюс назначение такой структуры данных тяжело представить.
14. Сделайте HashSet
из HashMap
(используйте только множество ключей, но не множество значений).
Set<Object> keySet = new HashSet<>(map.keySet());
15. Сделайте HashMap
из HashSet
(HashSet<Map.Entry<K, V>>)
.
Map<K, V> map = new HashMap<>(set.size());
for (Map.Entry<K, V> entry : set) {
map.put(entry.getKey(), entry.getValue());
}
Подборка по базе: Что такое спрос (автовосстановление).docx, Урок 14. Что такое почва..pptx, Что такое монархия.pptx, Конспект урока финансовой грамотности по теме _Что такое бизнес_, Эссе Что такое маркетинг.docx, Что такое честь.docx, Сочинение. Что такое патриотизм.docx, ЧТО ТАКОЕ ОС.pdf, что такое эргономика урок 2 15.09.22.docx, ЧТо такое осень.docx
41. Какое худшее время работы метода get(key) для ключа, которого нет в HashMap?
42. Какое худшее время работы метода get(key) для ключа, который есть в HashMap?
O(N). Худший случай — это поиск ключа в HashMap, вырожденного в список по причине совпадения ключей по hashCode() и для выяснения хранится ли элемент с определённым ключом может потребоваться перебор всего списка.
9. Функциональные интерфейсы
1. Что такое функциональный интерфейс? Примеры
2. Для чего нужна аннотация @FunctionalInterface?
3. Какие встроенные функциональные интерфейсы вы знаете?
Функциональный интерфейс — это интерфейс, который определяет только один абстрактный метод.
Основное назначение – использование в лямбда выражениях и method reference.
Чтобы точно определить интерфейс как функциональный, добавлена аннотация @FunctionalInterface, работающая по принципу @Override. Она обозначит замысел и не даст определить второй абстрактный метод в интерфейсе.
Интерфейс может включать сколько угодно default методов и при этом оставаться функциональным, потому что default методы — не абстрактные. встроенные функциональные интерфейсы:
— Predicate Проверяет соблюдение некоторого условия. Если оно соблюдается, то возвращается значение true. В качестве параметра лямбда-выражение принимает объект типа T
— Consumer выполняет некоторое действие над объектом типа T, при этом ничего не возвращая
— Function представляет функцию перехода от объекта типа T к объекту типа R
— Supplier не принимает никаких аргументов, но должен возвращать объект типа T
— UnaryOperator принимает в качестве параметра объект типа T, выполняет над ними операции и возвращает результат операций в виде объекта типа T
— BinaryOperator принимает в качестве параметра два объекта типа T, выполняет над ними бинарную операцию и возвращает ее результат также в виде объекта типа T
Predicate public interface Predicate { boolean test(T t); import java.util.function.Predicate;
} public class LambdaApp { public static void main(String[] args) {
Predicate
isPositive = x -> x > 0;
System.out.println(isPositive.test(5)); // true
System.out.println(isPositive.test(-7)); // false
}
}
BinaryOperator public interface
BinaryOperator {
T apply(T t1, T t2);
} import java.util.function.BinaryOperator; public class LambdaApp { public static void main(String[] args) {
BinaryOperator multiply = (x, y) -> x*y;
System.out.println(multiply.apply(3, 5)); // 15
System.out.println(multiply.apply(10, -2)); // -20
}
}
UnaryOperator public interface
UnaryOperator {
T apply(T t);
} import java.util.function.UnaryOperator; public class LambdaApp { public static void main(String[] args) {
UnaryOperator square = x -> x*x;
System.out.println(square.apply(5)); // 25
}
}
Function public interface Function {
R apply(T t);
} import java.util.function.Function; public class LambdaApp { public static void main(String[] args) {
Function convert = x-> String.valueOf(x)
+ » долларов»;
System.out.println(convert.apply(5)); // 5 долларов
}
}
Consumer public interface Consumer { void accept(T t);
} import java.util.function.Consumer; public class LambdaApp { public static void main(String[] args) {
Consumer printer = x-> System.out.printf(«%d долларов n», x); printer.accept(600); // 600 долларов
}
}
Supplier public interface Supplier {
T get();
} import java.util.Scanner; import java.util.function.Supplier; public class LambdaApp { public static void main(String[] args) {
Supplier userFactory = ()->{
Scanner in = new Scanner(System.in);
System.out.println(«Введите имя: «);
String name = in.nextLine(); return new User(name);
};
User user1 = userFactory.get();
User user2 = userFactory.get();
System.out.println(«Имя user1: » + user1.getName());
System.out.println(«Имя user2: » + user2.getName());
}
Введите имя:
Том
Введите имя:
Сэм
Имя user1: Том
Имя user2: Сэм
4. Что такое ссылка на метод?
Ссылки на методы (Method References) – это компактные лямбда выражения для методов, у которых уже есть имя.
Ссылки на методы бывают четырех видов:
— Ссылка на статический метод — ContainingClass::staticMethodName
Function
function = e -> Boolean.valueOf(e);
System.out.println(function.apply(«TRUE»));
Function function = Boolean::valueOf;
System.out.println(function.apply(«TRUE»));,
— Ссылка на нестатический метод конкретного объекта — containingObject::instanceMethodName
Consumer consumer = e -> System.out.println(e); consumer.accept(«OCPJP 8»);
Consumer consumer = System.out::println; consumer.accept(«OCPJP 8»);
— Ссылка на нестатический метод любого объекта конкретного типа
ContainingType::methodName
Function function = s -> s.toLowerCase();
System.out.println(function.apply(«OCPJP 8»));
Function function = String::toLowerCase;
System.out.println(function.apply(«OCPJP 8»));
— Ссылка на конструктор ClassName::new
Function function = (d) -> new Integer(d);
System.out.println(function.apply(«4»));
Function function = Integer::new;
System.out.println(function.apply(«4»));
5. Что такое лямбда-выражение? Чем его можно заменить?
Представляет набор инструкций, которые можно выделить в отдельную переменную и затем многократно вызвать в различных местах программы.
Образует реализацию метода, определенного в функциональном интерфейсе. При этом важно, что функциональный интерфейс должен содержать только один единственный метод без реализации. список параметров выражения -> тело лямбда-выражения (действия)
Параметры лямбда-выражения должны соответствовать по типу параметрам метода из функционального интерфейса. в лямбда-выражении использование обобщений не допускается. В этом случае нам надо типизировать объект интерфейса определенным типом, который потом будет применяться в лямбда-выражении public class LambdaApp { public static void main(String[] args) {
Operationable operation1 = (x, y)-> x + y;
Operationable operation2 = (x, y) -> x + y;
System.out.println(operation1.calculate(20, 10)); //30
System.out.println(operation2.calculate(«20», «10»)); //2010
}
} interface Operationable{
T calculate(T x, T y);
}
Одним из ключевых моментов в использовании лямбд является отложенное выполнение (deferred execution). То есть мы определяем в одном месте программы лямбда-выражение и затем можем его вызывать при необходимости неопределенное количество раз в различных частях программы. Отложенное выполнение может потребоваться, к примеру, в следующих случаях:
Выполнение кода отдельном потоке
Выполнение одного и того же кода несколько раз
Выполнение кода в результате какого-то события
Выполнение кода только в том случае, когда он действительно необходим и если он необходим
10. Stream API
1. Что такое Stream API? Для чего нужны стримы?
Интерфейс java.util.Stream представляет собой последовательность элементов, над которой можно производить различные операции.
Нужны для упрощения работы с наборами данных, в частности, упростить операции фильтрации, сортировки и другие манипуляции с данными.
IntStream.of(50, 60, 70, 80, 90, 100, 110, 120).filter(x -> x < 90).map(x -> x + 10)
.limit(3).forEach(System.out::print);
— создаем экземпляр Stream
Пустой стрим: Stream.empty()
Стрим из List: list.stream()
Стрим из Map: map.entrySet().stream()
Стрим из массива: Arrays.stream(array)
Стрим из указанных элементов: Stream.of(«1», «2», «3»)
Стрим из BufferedReader с помощью метода lines (); нужно закрывать close ().
— Промежуточные (“intermediate”, “lazy”) — обрабатывают поступающие элементы и возвращают стрим.
Может быть много, а может и не быть ни одной.
— Терминальные (“terminal”, ещё называют “eager”) — обрабатывают элементы и завершают работу стрима, может быть только один.
Важные моменты:
— Обработка не начнётся до тех пор, пока не будет вызван терминальный оператор. list.stream().filter(s —
> s > 5) (не возьмёт ни единого элемента из списка);
— Экземпляр стрима нельзя использовать более одного раза;
Коллекции
Streams
Конечны (хранят набор элементов)
Бесконечны
Индивидуальный доступ к элементам
Нет индивид. доступа к элементам
Можно менять (добавлять/удалять) элементы, в т.ч. через итератор
Если как то обрабатываем данные, то не влияет на источник
Кроме универсальных объектных существуют особые виды стримов для работы с примитивными типами данных int, long и double: IntStream, LongStream и DoubleStream. Эти примитивные стримы работают так же, как и обычные объектные, но со следующими отличиями:
— используют специализированные лямбда-выражения, например, IntFunction или IntPredicate вместо
Function и Predicate;
— поддерживают дополнительные конечные операции sum(), average(), mapToObj()
2. Почему Stream называют ленивым?
Ленивое программирование — технология, которая позволяет вам отсрочить вычисление кода до тех пор, пока не понадобится его результирующее значение.
Блок обработки – промежуточные операции не выполняются, пока не вызовется терминальная.
3. Какие существуют способы создания стрима?
Из коллекции:
Stream
fromCollection = Arrays.asList(«x», «y», «z»).stream();
Из набора значений:
Stream fromValues = Stream.of(«x», «y», «z»);
Из массива:
Stream fromArray = Arrays.stream(new String[]{«x», «y», «z»});
Из файла (каждая строка в файле будет отдельным элементом в стриме):
Stream fromFile = Files.lines(Paths.get(«input.txt»));
Из строки:
IntStream fromString = «0123456789».chars();
С помощью Stream.builder():
Stream fromBuilder = Stream.builder().add(«z»).add(«y»).add(«z»).build();
С помощью Stream.iterate() (бесконечный):
Stream fromIterate = Stream.iterate(1, n -> n + 1);
С помощью Stream.generate() (бесконечный):
Stream fromGenerate = Stream.generate(() -> «0»);
4. Как из коллекции создать стрим?
Stream fromCollection = Arrays.asList(«x», «y», «z»).stream();
5. Какие промежуточные методы в стримах вы знаете?
6. Расскажите про метод peek() “быстрый взгляд”.
— peek (принимает Consumer) integerStream.peek(System.out::println) позволяет подсмотреть какие элементы летают на данном этапе с помощью System.out::println
Stream peek(Consumer action);
7. Расскажите про метод map() “маппинг – из одного в другое”.
Отображение или маппинг позволяет задать функцию преобразования одного объекта в другой, то есть получить из элемента одного типа элемент другого типа. принимает Function.
Stream map(Function mapper); map(n -> n.toString())
8. Расскажите про метод flatMap(). “плоский маппинг”
Плоское отображение выполняется тогда, когда из одного элемента нужно получить несколько.
Stream
.of(«H e l l o», «w o r l d !»)
.flatMap((p) -> Arrays.stream(p.split(» «)))
.toArray(String[]::new);//[«H», «e», «l», «l», «o», «w», «o», «r», «l», «d», «!»]
Например, в примере выше мы выводим название телефона и его цену. Но что, если мы хотим установить для каждого телефона цену со скидкой и цену без скидки. То есть из одного объекта
Phone нам надо получить два объекта с информацией, например, в виде строки. Для этого применим flatMap:
Stream phoneStream = Stream.of(new Phone(«iPhone 6 S», 54000), new Phone(«Lumia 950»,
45000),
new Phone(«Samsung Galaxy S 6», 40000)); phoneStream
.flatMap(p->Stream.of(
String.format(«название: %s цена без скидки: %d», p.getName(), p.getPrice()),
String.format(«название: %s цена со скидкой: %d», p.getName(), p.getPrice() —
(int)(p.getPrice()*0.1))
))
.forEach(s->System.out.println(s));
9. Чем отличаются методы map() и flatMap().
10. Расскажите про метод filter()
Метод filter() является промежуточной операцией принимающей предикат, который фильтрует все элементы, возвращая только те, что соответствуют условию.
Stream
citiesStream = Stream.of(«Париж», «Лондон», «Мадрид»,»Берлин», «Брюссель»); citiesStream.filter(s->s.length()==6).forEach(s->System.out.println(s));
11. Расскажите про метод limit()
limit(long n) применяется для выборки первых n элементов потоков. Этот метод также возвращает модифицированный поток, в котором не более n элементов.
Stream phoneStream = Stream.of(«iPhone 6 S», «Lumia 950», «Samsung Galaxy S 6», «LG G 4»,
«Nexus 7»); phoneStream.skip(1)
.limit(2)
.forEach(s->System.out.println(s)); //
Lumia 950 Samsung Galaxy S 6
12. Расскажите про метод skip()
skip(long n) используется для пропуска n элементов. Этот метод возвращает новый поток, в котором пропущены первые n элементов.
13. Расскажите про метод sorted()
Для простой сортировки по возрастанию применяется метод sorted(). Подходит только для сортировки тех объектов, которые реализуют интерфейс Comparable.
Если же у нас классы объектов не реализуют этот интерфейс или мы хотим создать какую-то свою логику сортировки, то мы можем использовать другую версию метода sorted(), которая в качестве параметра принимает компаратор. class Phone{ private String name; private String company; private int price; public Phone(String name,
String comp, int price){ this.name=name; this.company=comp; this.price = price;
} public String getName() { return name; } public int getPrice() { return price; } public String getCompany() { return company; }
} import java.util.Comparator; import java.util.stream.Stream; public class Program { public static void main(String[] args) {
Stream phoneStream = Stream.of(new Phone(«iPhone
X», «Apple», 600), new Phone(«Pixel 2», «Google», 500), new Phone(«iPhone 8», «Apple»,450), new Phone(«Nokia 9», «HMD Global»,150), new Phone(«Galaxy S9», «Samsung», 300)); phoneStream.sorted(new PhoneComparator())
.forEach(p->System.out.printf(«%s (%s) — %d n», p.getName(), p.getCompany(), p.getPrice()));
}
}
class PhoneComparator implements Comparator
{ public int compare(Phone a, Phone b){ return a.getName().toUpperCase().compareTo(b.getName().toUpperCase());
}
}
14. Расскажите про метод distinct() “особый”
возвращает только ункальные элементы в виде потока
Stream
people = Stream.of(«Tom», «Bob», «Sam», «Tom», «Alice», «Kate», «Sam»); people.distinct().forEach(p -> System.out.println(p));
15. Какие терминальные методы в стримах вы знаете?
— forEach (принимает Consumer) например System.out::println (покажет все элементы, которые остались в стриме)
— findFirst () первый в порядке следования элемент из стрима (возвращается OptionalInt, т.к. может вернуться 0)
— allMatch (принимает предикат) позволяет удостовериться, удовлетворяют ли все элементы стрима определенному условию
— min () возвращает минимальный элемент из стрима
— count () возвращает количество элементов, оставшееся в стриме
— sum () 0
— collect () собирает элементы стрима в новое хранилище, например список. Куда может собрать – смотри список Collectors:
— groupingBy группирует по параметрам
— averagingInt среднеарифметическое какого то параметра
— summarizingInt дает кол-во элементов, суммму, мин, ср.арифм, макс значения
— reduce () – позволяет вычислить свертку элементов стрима (результат применения некоторого бинарного оператора к паре элементов из стрима, пока от стрима не останется один единсттвенный элемент) свёртка — это математическая операция, применённая к двум функциям f и g, порождающая третью функцию, которая иногда может рассматриваться как модифицированная версия одной из первоначальных
16. Расскажите про метод collect() “собирать”
собирает элементы стрима в новое хранилище, например список
Эта функция представляет объект Collector, который определен в пакете java.util.stream. Java уже предоставляет ряд встроенных функций, определенных в классе Collectors:
— toList(): преобразование к типу List
— toSet(): преобразование к типу Set
— toMap(): преобразование к типу Map
— groupingBy() — разделяет коллекцию на несколько частей и возвращает Map>;
— summingInt(), summingDouble(), summingLong() — возвращает сумму;
List filteredPhones = phones.stream()
.filter(s->s.length()<10)
.collect(Collectors.toList());
17. Расскажите про метод reduce() “уменьшить”
Позволяет выполнять какое-то действие на всей коллекции и возвращать один результат вычислим произведение набора чисел:
Stream numbersStream = Stream.of(1,2,3,4,5,6);
Optional
result = numbersStream.reduce((x,y)->x*y);
System.out.println(result.get()); // 720
18. Расскажите про класс Collectors и его методы.
В Java 8 в классе Collectors реализовано несколько распространённых коллекторов:
— toList(), toCollection(), toSet() — представляют стрим в виде списка, коллекции или множества;
— toConcurrentMap(), toMap() — позволяют преобразовать стрим в Map;
— averagingInt(), averagingDouble(), averagingLong() — возвращают среднее значение;
— summingInt(), summingDouble(), summingLong() — возвращает сумму;
— summarizingInt(), summarizingDouble(), summarizingLong() — возвращают SummaryStatistics с разными агрегатными значениями;
— -partitioningBy() — разделяет коллекцию на две части по соответствию условию и возвращает их как
Map;
— groupingBy() — разделяет коллекцию на несколько частей и возвращает Map>;
— mapping() — дополнительные преобразования значений для сложных Collector-ов.
— Так же существует возможность создания собственного коллектора через Collector.of():
Collector, List> toList = Collector.of(
ArrayList::new,
List::add,
(l1, l2) -> { l1.addAll(l2); return l1; }
);
Recommended textbook solutions
Fundamentals of Database Systems
7th Edition•ISBN: 9780133970777 (3 more)Ramez Elmasri, Shamkant B. Navathe
687 solutions
Service Management: Operations, Strategy, and Information Technology
7th Edition•ISBN: 9780077475864James Fitzsimmons, Mona Fitzsimmons
103 solutions
Starting Out with Python
4th Edition•ISBN: 9780134444321 (3 more)Tony Gaddis
629 solutions
Operating System Concepts
9th Edition•ISBN: 9781118063330 (2 more)Abraham Silberschatz, Greg Gagne, Peter B. Galvin
489 solutions
2017-04-15
Вы решили стать програмистом, поздравляем! Это отличный выбор профессии, которая по мимо удовольствия, приносит хороший доход и определенную независимость как от работодателя, так и от места дислокации.
У каждого из нас было первое в своей жизни собеседование. Для того что бы его не провалить, начинающий разработчик должен обладать определенным багажом знаний и умений.
Очевидно, что все знать не возможно. Даже создатель языка C++ Бьярне Страуструп говорил, что сам не знает свой язык полностью.
Вы должны обладать определенными базовыми основами языка программирования и знать как их применить.
Поэтому предлагаем вам небольшую подборку-Вопросы на собеседовании junior java developer.
ООП (Объектно-ориентированное программирование)
- Что такое ООП?
- Что такое объект?
- Назовите основные принципы ООП.
- Что такое наследование?
- Что такое полиморфизм? Какие проявления полиморфизма в Java Вы знаете?
- Что такое инкапсуляция?
- Что такое aбстракция?
- В чем преимущества объектно-ориентированных языков программирования?
- Как использование объектно – ориентерованного подхода улучшает разработку программного обеспечения?
- Имеется выражение «является» и «имеет». Что они подразумевают в плане принципов ООП? В чем разница между композицией и агрегацией?
- Что вы подразумеваете под полиморфизмом, инкапсуляцией и динамическим связыванием?
Java core
- Чем отличается JRE, JVM и JDK?
- Опишите модификаторы доступа в Java.
- Что такое package level access.
- Чем абстрактный клас отличается от интерфейса? В каких случаях Вы бы использовали абстрактный класс, а в каких интерфейс?
- Может ли объект получить доступ к private-переменной класса? Если, да, то каким образом?
- Для чего в джаве статические блоки?
- Можно ли перегрузить static метод?
- Расскажите про внутренние классы. Когда вы их будете использовать?
- В чем разница между переменной экземпляра и статической переменной? Приведите пример.
- Приведите пример когда можно использовать статический метод?
- Расскажите про классы- загрузчики и про динамическую зарузку классов.
- Что такое статическая и что такое динамическая загрузка класса?
- Для чего нужен оператор “assert” в джава?
- Почему в некоторых интерфейсах вообще не определяют методов?
- Какая основная разница между String, StringBuffer, StringBuilder?
- Расскажите про потоки ввода-вывода Java.
- Что такое Heap и Stack память в Java?
- Какая разница между Stack и Heap памятью в Java?
- Расскажите про модель памяти в джава?
- Как работает сборщик мусора (garbage collector)?
- Расскажите про приведение типов. Что такое понижение и повышение типа? Когда вы получаете ClassCastException?
- Что такое статический класс, какие особенности его использования?
- Каким образом из вложенного класса получить доступ к полю внешнего класса.
- Какие существуют типы вложенных классов? Для чего они используются?
- Возможно ли при переопределении (override) метода изменить:
- Модификатор доступа
- Возвращаемый тип
- Тип аргумента или количество
- Имя аргументов
- Изменять порядок, количество или вовсе убрать секцию throws?
- Что такое autoboxing?
- Что такое Generics?
- Какова истинная цель использования обобщенных типов в Java?
- Каким образом передаются переменные в методы, по значению или по ссылке?
- Какие методы есть у класса Object?
- Правила переопределения метода Object.equals().
- Если вы хотите переопределить equals(), какие условия должны удовлетворяться для переопределенного метода?
- Какая связь между hashCode и equals?
- Каким образом реализованы методы hashCode и equals в классе Object?
- Что будет, если переопределить equals не переопределяя hashCode? Какие могут возникнуть проблемы?
- Есть ли какие-либо рекомендации о том, какие поля следует использовать при подсчете hashCode?
- Для чего нужен метод hashCode()?
- Правила переопределения метода Object.hashCode().
- Расскажите про клонирование объектов. В чем отличие между поверхностным и глубоким клонированием?
- Правила переопределения метода Object.clone().
- Где и как вы можете использовать закрытый конструктор?
- Что такое конструктор по умолчанию?
- Опишите метод Object.finalize().
- Чем отличаются слова final, finally и finalize?
- Опишите иерархию исключений.
- Какие виды исключений в Java вы знаете, чем они отличаются?
- Что такое checked и unchecked Exception?
- Как создать свой unchecked Exception?
- Какие есть Unchecke exeption?
- Что такое Error?
- Опишите работу блока try-catch-finally.
- Возможно ли использование блока try-finally (без catch)?
- Всегда ли исполняется блок finally?
- Какие есть оссобенности класса String? что делает метод intern().
- Можно ли наследовать строковый тип, почему?
- Почему строка является популярным ключом в HashMap в Java?
- Дайте определение понятию конкатенация строк.
- Как перевернуть строку?
- Как сравнить значение двух строк?
- Как обрезать пробелы в начале и конце строки?
- Дайте определение понятию “пул строк”.
- Можно ли синхронизировать доступ к строке?
- Как правильно сравнить значения строк двух различных объектов типа String и StringBuffer?
- Почему строка неизменная и финализированная в Java?
- Напишите метод удаления данного символа из строки.
- Что такое рефлексия?
- Что произойдет со сборщиком мусора (GC), если во время выполнения метода finalize() некоторого объекта произойдет исключение?
- Что такое интернационализация, локализация?
- Что такое Аннотации в Java?
- Какие функции выполняет Аннотации?
- Какие встроенные аннотации в Java вы знаете?
- Что делают аннотации @Retention, @Documented, @Target и @Inherited?
- Что делают аннотации @Override, @Deprecated, @SafeVarargs и @SuppressWarnings?
- Какой жизненный цикл аннотации можно указать с помощью @Retention?
- К каким элементам можно применять аннотацию, как это указать?
- Как создать свою Аннотацию?
- Атрибуты каких типов допустимы в аннотациях?
- Что такое JMX?
- Какие выгоды предлагает JMX?
- Что еще умеет JMX кроме дистанционного управления?
- Что такое MBean?
- Какие типы MBeans существуют?
- Что такое MBean Server?
- Какие механизмы обеспечивают безопасность в технологии Java?
- Назовите несколько видов проверок которые выполняет верификатор байт-кода Java?
- Что вы знаете о “диспетчере защиты” в Java?
- Что такое JAAS?
- Что такое Рефакторинг?
Java collections framework (JCF)- это набор связанных классов и интерфейсов, реализующих commonly reusable collection структур данных.
- Что такое Коллекция?
- Назовите основные интерфейсы коллекций и их имплементации.
- Чем отличается ArrayList от LinkedList? В каких случаях лучше использовать первый, а в каких второй?
- Чем отличается HashMap от Hashtable?
- Чем отличается ArrayList от Vector?
- Как сравниваются елементы коллекций?
- Расположите в виде иерархии следующие интерфейсы: List, Set, Map, SortedSet, SortedMap, Collection,Iterable, Iterator, NavigableSet, NavigableMap.
- Почему Map – это не Collection, в то время как List и Set являются Collection?
- Дайте определение понятию “iterator”.
- Что вы знаете об интерфейсе Iterable?
- Как одной строчкой преобразовать HashSet в ArrayList?
- Как одной строчкой преобразовать ArrayList в HashSet?
- Как перебрать все ключи Map учитывая, что Map – это не Iterable?
- Как перебрать все значения Map учитывая, что Map – это не Iterable?
- Как перебрать все пары ключ-значение в Map учитывая, что Map – это не Iterable?
- В чем проявляется “сортированность” SortedMap, кроме того, что toString() выводит все по порядку?
- Как одним вызовом копировать элементы из любой Collection в массив?
- Реализуйте симметрическую разность двух коллекций используя методы Collection (addAll(), removeAll(), retainAll()).
- Сравните Enumeration и Iterator.
- Как между собой связаны Iterable и Iterator?
- Как между собой связаны Iterable, Iterator и “for-each ” введенный в Java 5?
- Сравните Iterator и ListIterator.
- Что произойдет, если я вызову Iterator.next() не “спросив” Iterator.hasNext()?
- Что произойдет, если я вызову Iterator.next() перед этим 10 раз вызвав Iterator.hasNext()? Я пропущу 9 элементов?
- Если у меня есть коллекция и порожденный итератор, изменится ли коллекция, если я вызову iterator.remove()?
- Если у меня есть коллекция и порожденный итератор, изменится ли итератор, если я вызову collection.remove(..)?
- Зачем добавили ArrayList, если уже был Vector?
- В реализации класса ArrayList есть следующие поля: Object[] elementData, int size. Объясните, зачем хранить отдельно size, если всегда можно взять elementData.length?
- LinkedList – это односвязный, двусвязный или четырехсвязный список?
- Какое худшее время работы метода contain() для элемента, который есть в LinkedList (O(1), O(log(N)), O(N), O(N*log(N)), O(N*N))?
- Какое худшее время работы метода contain() для элемента, который есть в ArrayList (O(1), O(log(N)), O(N), O(N*log(N)), O(N*N))?
- Какое худшее время работы метода add() для LinkedList (O(1), O(log(N)), O(N), O(N*log(N)), O(N*N))?
- Какое худшее время работы метода add() для ArrayList (O(1), O(log(N)), O(N), O(N*log(N)), O(N*N))?
- Сколько выделяется элементов в памяти при вызове ArrayList.add()?
- Сколько выделяется элементов в памяти при вызове LinkedList.add()?
- Оцените количество памяти на хранение одного примитива типа byte в LinkedList?
- Оцените количество памяти на хранение одного примитива типа byte в ArrayList?
- Я добавляю элемент в середину List-а: list.add(list.size()/2, newElem). Для кого эта операция медленнее – для ArrayList или для LinkedList?
- Как перебрать элементы LinkedList в обратном порядке, не используя медленный get(index)?
- Как одним вызовом из List получить List со всеми элементами, кроме первых и последних 3-х?
- Могут ли у разных объектов в памяти (ref0 != ref1) быть ref0.hashCode() == ref1.hashCode()?
- Могут ли у разных объектов в памяти (ref0 != ref1) быть ref0.equals(ref1) == true?
- Могут ли у разных ссылок на один объект в памяти (ref0 == ref1) быть ref0.equals(ref1) == false?
- Есть класс Point{int x, y;}. Почему хэш-код в виде 31 * x + y предпочтительнее чем x + y?
- Если у класса Point{int x, y;} “правильно ” реализовать метод equals (return ref0.x == ref1.x && ref0.y == ref1.y), но сделать хэш-код в виде int hashCode() {return x;}, то будут ли корректно такие точки помещаться и извлекаться из HashSet?
- equals() порождает отношение эквивалентности. Какими из свойств обладает такое отношение: коммутативность, симметричность, рефлексивность, дистрибутивность, ассоциативность, транзитивность?
- Можно ли так реализовать equals(Object that) {return this.hashCode() == that.hashCode()}?
- В equals требуется проверять, что аргумент (equals(Object that)) такого же типа как и сам объект. В чем разница между this.getClass() == that.getClass() и that instanceof MyClass?
- Можно ли реализовать метод equals класса MyClass вот так: class MyClass {public boolean equals(MyClass that) {return this == that;}}?
- Будет ли работать HashMap, если все ключи будут возвращать int hashCode() {return 42;}?
- Зачем добавили HashMap, если уже был Hashtable?
- Согласно Кнуту и Кормену существует две основных реализации хэш-таблицы: на основе открытой адресацией и на основе метода цепочек. Как реализована HashMap? Почему так сделали (по вашему мнению)? В чем минусы и плюсы каждого подхода?
- Сколько переходов по ссылкам происходит, когда вы делаете HashMap.get(key) по ключу, который есть в таблице?
- Сколько создается новых объектов, когда вы добавляете новый элемент в HashMap?
- Как работает HashMap при попытке сохранить в нее два элемента по ключам с одинаковым hashCode, но для которых equals == false?
- HashMap может выродиться в список даже для ключей с разным hashCode. Как это возможно?
- Какое худшее время работы метода get(key) для ключа, которого нет в таблице (O(1), O(log(N)), O(N), O(N*log(N)), O(N*N))?
- Какое худшее время работы метода get(key) для ключа, который есть в таблице (O(1), O(log(N)), O(N), O(N*log(N)), O(N*N))?
- Объясните смысл параметров в конструкторе HashMap(int initialCapacity, float loadFactor).
- В чем разница между HashMap и IdentityHashMap? Для чего нужна IdentityHashMap? Как может быть полезна для реализации сериализации или клонирования?
- В чем разница между HashMap и WeakHashMap? Для чего нужна WeakHashMap?
- В WeakHashMap используются WeakReferences. А почему бы не создать SoftHashMap на SoftReferences?
- В WeakHashMap используются WeakReferences. А почему бы не создать PhantomHashMap на PhantomReferences?
- Сделайте HashSet из HashMap (используйте только множество ключей, но не множество значений).
- Сделайте HashMap из HashSet (HashSet<Map.Entry<K, V>>).
- Сравните интерфейсы java.util.Queue и java.util.Deque.
- Кто кого расширяет: Queue расширяет Deque, или Deque расширяет Queue?
- Почему LinkedList реализует и List, и Deque?
- В чем разница между классами java.util.Arrays и java.lang.reflect.Array?
- В чем разница между классами java.util.Collection и java.util.Collections?
- Напишите НЕмногопоточную программу, которая заставляет коллекцию выбросить ConcurrentModificationException.
- Что такое “fail-fast поведение”?
- Для множеств еnum-ов есть специальный класс java.util.EnumSet? Зачем? Чем авторов не устраивал HashSet или TreeSet?
- java.util.Stack – считается “устаревшим”. Чем его рекомендуют заменять? Почему?
- Какая коллекция реализует дисциплину обслуживания FIFO?
- Какая коллекция реализует дисциплину обслуживания FILO?
- Приведите пример, когда какая-либо коллекция выбрасывает UnsupportedOperationException.
- Почему нельзя написать “ArrayList<List> numbers = new ArrayList<ArrayList>();” но можно “List<ArrayList> numbers = new ArrayList<ArrayList>();”?
- LinkedHashMap – что это еще за “зверь”? Что в нем от LinkedList, а что от HashMap?
- LinkedHashSet – что это еще за “зверь”? Что в нем от LinkedList, а что от HashSet?
- Говорят, на LinkedHashMap легко сделать простенький кэш c “invalidation policy”, знаете как?
- Что позволяет сделать PriorityQueue?
- В чем заключаются отличия java.util.Comparator от java.lang.Comparable?
- Какие нововведения, появились в Java 8?
- Как сортировать список строк с помощью лямбда-выражения?
- Какова структура лямбда-выражения?
- К каким переменным есть доступ у Лямбда-выражений?
- Что такое ссылки на метод?
- Какие виды ссылок на методы вы знаете?
- Объясните выражение System.out::println.
- Что такое функциональные интерфейсы?
- Для чего нужен функциональный интерфейс BiConsumer<T,U>?
- Для чего нужен функциональный интерфейс BiFunction<T,U,R>?
- Для чего нужен функциональный интерфейс BinaryOperator<T>?
- Для чего нужен функциональный интерфейс BiPredicate<T,U>?
- Для чего нужен функциональный интерфейс BooleanSupplier?
- Для чего нужен функциональный интерфейс Consumer<T>?
- Для чего нужен функциональный интерфейс DoubleBinaryOperator?
- Для чего нужен функциональный интерфейс DoubleConsumer?
- Для чего нужен функциональный интерфейс DoubleFunction<R>?
- Для чего нужен функциональный интерфейс DoublePredicate?
- Для чего нужен функциональный интерфейс DoubleSupplier?
- Для чего нужен функциональный интерфейс DoubleToIntFunction?
- Для чего нужен функциональный интерфейс DoubleToLongFunction?
- Для чего нужен функциональный интерфейс DoubleUnaryOperator?
- Для чего нужен функциональный интерфейс Function<T,R>?
- Для чего нужен функциональный интерфейс IntBinaryOperator?
- Для чего нужен функциональный интерфейс IntConsumer?
- Для чего нужен функциональный интерфейс IntFunction<R>?
- Для чего нужен функциональный интерфейс IntPredicate?
- Для чего нужен функциональный интерфейс IntSupplier?
- Для чего нужен функциональный интерфейс IntToDoubleFunction?
- Для чего нужен функциональный интерфейс IntToLongFunction?
- Для чего нужен функциональный интерфейс IntUnaryOperator?
- Для чего нужен функциональный интерфейс LongBinaryOperator?
- Для чего нужен функциональный интерфейс LongConsumer?
- Для чего нужен функциональный интерфейс LongFunction<R>?
- Для чего нужен функциональный интерфейс LongPredicate?
- Для чего нужен функциональный интерфейс LongSupplier?
- Для чего нужен функциональный интерфейс LongToDoubleFunction?
- Для чего нужен функциональный интерфейс LongToIntFunction?
- Для чего нужен функциональный интерфейс LongUnaryOperator?
- Для чего нужен функциональный интерфейс ObjDoubleConsumer<T>?
- Для чего нужен функциональный интерфейс ObjIntConsumer<T>?
- Для чего нужен функциональный интерфейс ObjLongConsumer<T>?
- Для чего нужен функциональный интерфейс Predicate<T>?
- Для чего нужен функциональный интерфейс Supplier<T>?
- Для чего нужен функциональный интерфейс ToDoubleBiFunction<T,U>?
- Для чего нужен функциональный интерфейс ToDoubleFunction<T>?
- Для чего нужен функциональный интерфейс ToIntBiFunction<T,U>?
- Для чего нужен функциональный интерфейс ToIntFunction<T>?
- Для чего нужен функциональный интерфейс ToLongBiFunction<T,U>?
- Для чего нужен функциональный интерфейс ToLongFunction<T>?
- Для чего нужен функциональный интерфейс UnaryOperator<T>?
- Что такое StringJoiner?
- Что такое default методы?
- Что такое static методы?
- Как вызывать default-метод интерфейса в классе?
- Как вызывать static-метод интерфейса в классе?
- Что такое потоки(stream) в Java 8
- Для чего нужен метод collect Java 8?
- В чем разница между коллекцией(Collection) и потоком(Stream)?
- Для чего предназначен метод forEach в потоках(stream)?
- Как вывести на экран 10 случайных чисел, используя forEach?
- Для чего предназначен метод map в потоках(stream)?
- Как можно вывести на экран уникальные квадраты чисел используя метод map?
- Какова цель метода filter в потоках(stream)?
- Как вывести на экран количество пустых строк с помощью метода filter?
- Для чего предназначен метод limit в потоках(stream)?
- Для чего предназначен метод sorted в потоках(stream)?
- Как вывести на экран 10 случайных чисел в отсортированном порядке в Java 8?
- Параллельная обработка в Java 8?
- Как найти максимальное число в списке Java 8?
- Как найти минимальное число в списке Java 8?
- Как получить сумму всех чисел в списке, используя Java 8?
- Как получить среднее значение всех чисел, в списке, используя Java 8?
- Что такое Optional?
- Что такое Nashorn?
- Что такое jjs в Java 8?
- Что такое LocalDateTime в Java 8?
- Что такое ZonedDateTime в Java 8?
- Как получить текущую дату с использованием time API из Java 8?
- Как добавить 1 неделю к текущей дате с использованием time API?
- Как добавить 1 месяц к текущей дате с использованием time API?
- Как добавить 1 год к текущей дате с использованием time API?
- Как добавить 10 лет к текущей дате с использованием time API?
- Как получить следующий вторник используя time API?
- Как получить вторую субботу текущего месяца используя time API?
- Как получить текущею дату в миллисекундах используя time API?
- Як получить текущею дату по местному времени в миллисекундах используя используя time API?
- Какой класс появился в Java 8 для декодирования данных?
- Какой класс появился в Java 8 для кодирования данных?
- Как создать Base64 декодировщик?
- Как создать Base64 кодировщик?
Потоки ввода вывода в java
- Что такоє символьная ссылка?
- Какая разница между I/О и NIO?
- Какие особенности NIO вы знаете?
- Какие существуют виды потоков ввода/вывода?
- Назовите основные классы потоков ввода/вывода.
- Чем отличаются и что общего у OutputStream, InputStream, Writer, Reader?
- Какие подклассы базового класса InputStream ви знаєте, для чего они предназначены?
- Что вы знаете о RandomAccessFile?
- Какие есть режимы доступа к файлу есть у RandomAccessFile ?
- Какие подклассы базового класса OutputStream ви знаєте, для чего они предназначены?
- Для чего используется PushbackInputStream?
- Для чего используется SequenceInputStream?
- Какие подклассы базового класса Reader ви знаєте, для чего они предназначены?
- Какие подклассы базового класса Writer ви знаєте, для чего они предназначены?
- Что такое абсолютный путь и относительный путь?
- В каких пакетах лежат классы-потоки?
- Что вы знаете о классах-надстройках?
- Какой класс-надстройка позволяет читать данные из входного байтового потока в формате примитивных типов данных?
- Какой класс-надстройка позволяет ускорить чтение/запись за счет использования буфера?
- Какие классы позволяют преобразовать байтовые потоки в символьные и обратно?
- В чем отличие класса PrintWriter от PrintStream?
- Какой класс предназначен для работы с элементами файловой системы?
- Какой символ является разделителем при указании пути в файловой системе?
- Какие методы класса File ви знаєте?
- Что вы знаете об интерфейсе FileFilter?
- Какие классы позволяют архивировать объекты?
Многопоточность в Java (multithreading)
- Чем отличается процесс от потока?
- Каким образом можно создать поток?
- Что такое монитор?
- Какие способы синхронизации в Java?
- Как работают методы wait и notify/notifyAll?
- Чем отличается работа метода wait с параметром и без параметра?
- Как работает метод Thread.yield()? Чем отличаются методы Thread.sleep() и Thread.yield()?
- Как работает метод Thread.join()?
- Что такое dead lock?
- На каком объекте происходит синхронизация при вызове static synchronized метода?
- Для чего используется ключевое слово volatile, synchronized, transient, native?
- Что значит приоритет потока?
- Что такое потоки –демоны в джава?
- Что значит усыпить поток?
- В каких состояниях может быть поток в джава? Как вообще работает поток?
- Чем отличаются два интерфейса для реализации задач Runnable и Callable?
- Различия между CyclicBarrier и CountDownLatch?
- Что такое состояние гонки (race condition)?
- Как остановить нить?
- Что происходит, когда в нити появляется исключение?
- Что такое ThreadLocal переменная?
- Что такое FutureTask?
- Различие между interrupted и isInterrupted?
- Почему методы wait и notify вызываются в синхронизированном блоке?
- Что такое пул нитей?
- Различия между livelock и deadlock?
- Как проверить, удерживает ли нить lock?
- Как получить дамп нити?
- Какой JVM параметр используется для контроля размера стека нити?
- Различия между synchronized и ReentrantLock?
- Что такое Semaphore?
- Что будет, если очередь пула нитей уже заполнена, а вы подадите задачу?
- Различия между методами submit() и execute() у пула нитей?
- Что такое блокирующий метод?
- Что такое ReadWriteLock?
- Что такое double checked locking Синглтона?
- Что такое фреймворк Fork/Join?