java что такое map
Map в Java с примерами
Интерфейс Java Map, java.util.Map, представляет отображение между ключом и значением. В частности, может хранить пары ключей и значений. Каждый ключ связан с определенным значением. После сохранения на карте вы можете позже найти значение, используя только ключ.
Интерфейс не является подтипом интерфейса Collection. Следовательно, он немного отличается от остальных типов коллекций.
Реализация
Поскольку Map является интерфейсом, вам необходимо создать конкретную реализацию интерфейса для его использования. API коллекций содержит следующие:
Наиболее часто используемые реализации – это HashMap и TreeMap. Каждая из них ведет себя немного по-разному в отношении порядка элементов при итерации карты и времени (большая запись 0), необходимого для вставки и доступа к элементам в картах.
Вот несколько примеров того, как создать экземпляр:
Вставка элементов
Чтобы добавить элементы, вы вызываете ее метод put(). Вот несколько примеров:
Три вызова put() отображают строковое значение на строковый ключ. Затем вы можете получить значение, используя этот ключ.
Только объекты могут быть вставлены
Только объекты могут быть использованы в качестве ключей и значений. Если вы передаете примитивные значения (например, int, double и т. Д.) в качестве ключа или значения, они будут автоматически упакованы перед передачей в качестве параметров. Вот пример параметров примитива auto-boxing, передаваемых методу put():
Значение, переданное методу put() в приведенном выше примере, является примитивом int. Java автоматически упаковывает его внутри экземпляра Integer, поскольку для put() в качестве ключа и значения требуется экземпляр Oject. Автобокс также может произойти, если вы передадите примитив в качестве ключа.
Последующие вставки с тем же ключом
Заданный ключ может появляться на карте только один раз. Это означает, что только одна пара ключ + значение для каждого из них может существовать одновременно. Другими словами, для ключа «key1» в одном экземпляре может храниться только одно значение. Конечно, вы можете хранить значения одного и того же ключа в разных экземплярах карты.
Если вы вызываете put() более одного раза с одним и тем же ключом, последнее заменяет существующее значение для данного ключа.
Нулевые ключи не допускаются
Обратите внимание, что ключ не может быть нулевым!
Карта использует методы ключа hashCode() и equals() для внутреннего хранения пары ключ-значение, поэтому, если ключ имеет значение null, карта не может правильно разместить пару внутри.
Допустимы нулевые значения
Значение пары ключ + значение, хранящееся на карте, может быть нулевым, так что это совершенно правильно:
Просто имейте в виду, что вы получите null, когда позже вызовете get() с этим ключом:
Переменная value будет иметь значение null после выполнения этого кода, если нулевое значение было вставлено для этого ключа ранее (как в предыдущем примере).
Вставка всех элементов с другой карты
После выполнения этого кода карта, на которую ссылается переменная mapB, будет содержать обе записи ключ + значение, вставленные в mapA в начале примера кода.
Копирование записей идет только в одну сторону. Вызов mapB.putAll(mapA) будет копировать только записи из mapA в mapB, а не из mapB в mapA. Чтобы скопировать записи другим способом, вам нужно будет выполнить код mapA.putAll(mapB).
Как получить элементы
Чтобы получить определенный элемент вы вызываете его метод get(), передавая ключ для этого элемента в качестве параметра:
Обратите внимание, что метод get() возвращает Java-объект, поэтому мы должны привести его к String(поскольку мы знаем, что значение является String). Позже в этом руководстве по Java Map вы увидите, как использовать Java Generics для ввода Map, чтобы она знала, какие конкретные типы ключей и значений она содержит. Это делает ненужным приведение типов и усложняет случайное добавление неправильных значений в карту.
Возвращение значения по умолчанию
Интерфейс имеет метод getOrDefault(), который может возвращать значение по умолчанию, предоставленное вами – в случае, если никакое значение не сохранено с помощью данного ключа:
В этом примере создается карта и в ней хранятся три значения с использованием ключей A, B и C. Затем вызывается метод Map getOrDefault(), передавая в качестве ключа строку String E вместе со значением по умолчанию – значением String по умолчанию. Поскольку карта не содержит объектов, хранящихся в ключе E, будет возвращено заданное значение по умолчанию.
Проверка содержится ли ключ
Используется метод containsKey():
После выполнения этого кода переменная hasKey будет иметь значение true, если пара ключ + значение была вставлена ранее с помощью строкового ключа 123, и false, если такая пара ключ + значение не была вставлена.
Проверка содержится ли значение
Используется метод containsValue():
После выполнения этого кода переменная hasValue будет содержать значение true, если пара ключ-значение была вставлена раньше, со строковым значением «значение 1», и false, если нет.
Перебор ключей
Существует несколько способов итерации ключей, хранящихся на карте. Наиболее часто используемые методы:
Все методы будут рассмотрены в следующих разделах.
Использование ключевого итератора
С помощью метода keySet():
Как вы можете видеть, ключ Iterator возвращает каждый ключ, сохраненный в Map, один за другим (по одному для каждого вызова next()). Получив ключ, вы можете получить элемент, сохраненный для этого ключа, с помощью метода get().
Использование цикл for-each
В Java 5 вы также можете использовать цикл for-each для итерации ключей, хранящихся на карте:
Эффект приведенного выше кода очень похож на код, показанный в предыдущем разделе.
Использование ключевого потока
Интерфейс Stream является частью Java Stream API, который был добавлен в Java 8. Сначала вы получаете ключ Set из карты, и из него вы можете получить Stream:
Итерация значений
Также возможно просто перебрать значения, хранящиеся в Map, с помощью метода values(). Варианты:
Все эти параметры описаны в следующих разделах.
Использование Value Iterator
Первый способ – это получить экземпляр итератора значения из значения Set и выполнить итерацию:
Поскольку набор неупорядочен, у вас нет никаких гарантий относительно порядка, в котором значения повторяются.
Использование for-each
Второй метод – это цикл Java for-each:
В этом примере будут распечатаны все значения, хранящиеся в переменной mapA MapA.
Использование потока
Подразумевает использование потока значений с помощью API-интерфейса Stream. Сначала вы получаете значение Set из карты, а из значения Set вы можете получить поток:
Итерация записей
Под записями подразумеваются пары ключ + значение. Существует два способа:
Обе эти опции будут объяснены в следующих разделах.
Использование итератора ввода
Обратите внимание, как ключ и значение могут быть получены из каждого экземпляра Map.Entry.
Имейте в виду, что приведенный выше код можно сделать немного лучше, используя Map, типизированную с помощью Generics, как показано далее в этом руководстве.
Использование For-Each
Обратите внимание, что этот пример тоже можно сделать немного красивее, используя универсальную карту.
Удаление записей
Вы удаляете записи, вызывая метод remove(Object key). Таким образом, вы удаляете пару (ключ, значение), соответствующую ключу:
После выполнения этой инструкции карта, на которую ссылается mapA, больше не будет содержать запись (пара ключ + значение) для ключа key1.
Удаление всех записей
Используется метод clear():
Замена записи
Можно заменить элемент, используя метод replace(). Он будет вставлять новое значение только в том случае, если к ключу сопоставимо существующее значение. В ином случае никакое значение не вставлено. Это отличается от того, как работает метод put(), который всегда вставляет значение, несмотря ни на что.
После выполнения этого кода экземпляр Map будет содержать более новое значение String для ключа String.
Количество записей
Вы можете узнать количество записей, используя метод size(). Количество записей в Java-карте также называется размером карты – отсюда и имя метода size(). Вот пример:
Проверка, пуста ли карта
Интерфейс имеет специальный метод для проверки isEmpty() и возвращает:
Общие карты
По умолчанию вы можете поместить любой объект в карту, но Generics из Java 5 позволяет ограничить типы объектов, которые вы можете использовать как для ключей, так и для значений в карте:
Эта карта теперь может принимать только объекты String для ключей и экземпляры MyObject для значений. Затем вы можете получить доступ к итерированным ключам и значениям без их приведения. Вот как это выглядит:
Функциональные операции
Интерфейс имеет несколько функциональных операций, добавленных из Java 8. Они позволяют взаимодействовать с Map в более функциональном стиле. Например, вы передаете лямбда-выражение в качестве параметра этим методам. Функциональные методы работы:
compute()
Метод принимает ключевой объект и лямбда-выражение в качестве параметров. Лямбда-выражение должно реализовывать интерфейс java.util.function.BiFunction. Вот пример:
В приведенном выше примере вы можете видеть, что лямбда-выражение проверяет, является ли значение, сопоставленное данному ключу, нулевым или нет, перед вызовом toString(). ToUpperCase() для него.
computeIfAbsent()
Метод Map computeIfAbsent() работает аналогично методу compute():
Этот пример на самом деле просто возвращает постоянное значение – строку 123. Однако лямбда-выражение могло вычислить значение любым необходимым способом – например, извлечь значение из другого объекта или объединить его с другими значениями и т. д.
computeIfPresent()
Метод работает противоположно computeIfAbsent(). Он вызывает только лямбда-выражение, переданное ему в качестве параметра, если в Map уже существует запись для этого ключа:
merge()
Метод принимает в качестве параметров ключ, значение и лямбда-выражение, реализующее интерфейс BiFunction.
Ответы на самые популярные вопросы об интерфейсе Map
0. Как перебрать все значения Map
1. Как конвертировать Map в List
2. Как отсортировать ключи мапы
Поместить Map.Entry в список и отсортировать его, используя Comparator.
В компараторе будем сравнивать исключительно ключи пар:
Если разобрался с лямбдами, эту запись можно существенно сократить:
И, конечно, все можно переписать, используя лямбды:
В отличие от первого способа, используя SortedMap, мы всегда будем хранить данные в отсортированном виде.
3. Как отсортировать значения мапы
4. В чем разница между HashMap, TreeMap, и Hashtable
Порядок элементов. HashMap и Hashtable не гарантируют, что элементы будут храниться в порядке добавления. Кроме того, они не гарантируют, что порядок элементов не будет меняться со временем. В свою очередь, TreeMap гарантирует хранение элементов в порядке добавления или же в соответствии с заданным компаратором.
Допустимые значения. HashMap позволяет иметь ключ и значение null, HashTable — нет. TreeMap может использовать значения null только если это позволяет компаратор. Без использования компаратора (при хранении пар в порядке добавления) значение null не допускается.
Синхронизация. Только HashTable синхронизирована, остальные — нет. Если к мапе не будут обращаться разные потоки, рекомендуется использовать HashMap вместо HashTable.
И общее сравнение реализаций:
HashMap | HashTable | TreeMap | |
---|---|---|---|
Упорядоченность элементов | нет | нет | да |
null в качестве значения | да | нет | да/нет |
Потокобезопасность | нет | да | нет |
Алгоритмическая сложность поиска элементов | O(1) | O(1) | O(log n) |
Структура данных под капотом | хэш-таблица | хэш-таблица | красно-чёрное дерево |
5. Как создать двунаправленную мапу
6. Как создать пустую Map
Обычная инициализация объекта:
Создание неизменяемой (immutable) пустой мапы:
Java что такое map
В интерфейсе java.util.Map параметризуются два типа, это ключ и значение.
Объявление java.util.Map выглядит как:
При этом стоит отметить, что не может быть повторяющихся ключей, что следует из названия и смысла Map : каждому ключу соответствует значение.
Вопрос:
Ответ:
Класс java.util.Dictionary является полностью абтрактным, без какого-либо состояния.
Но благодаря WORA в Java просто так ничего не меняют и не удаляют, поэтому старые классы остались, а новую иерархию начали строить с интерфейсов.
Основные методы, которые предоставляет интерфейс java.util.Map :
Это значит, что все реализации интерфейса java.util.Map позволяют доставать, добавлять и удалять элементы по ключам, а также предоставлять множество ключей и коллекцию хранимых значений.
Как уже было сказано выше, Map это набор пар ключ-значение.
Объявление java.util.Map#Entry выглядит как:
И предоставляет методы, позволяющие получить/установить ключ и значение.
Помимо всего прочего предоставляются также компараторы для сравнения пар по ключу и значению.
Объявление выглядит следующим образом:
Объявление выглядит следующим образом:
Иерархия классов выглядит следующим образом:
Абстрактный класс java.util.AbstractMap предоставляет заготовку для последующих реализаций.
Что говорит о том, что операция не поддерживется и ее надо либо не использовать, либо переопределить метод.
Наиболее известные реализации java.util.Map :
Когда и какую реализацию выбрать?
Если порядок хранения элементов не важен, то выбор java.util.HashMap более чем оправдан.
Помните, что java.util.TreeMap не поддерживает работу с null ключами.
Структура данных | Производительность (basic ops) | Память | Отсортированность элементов | Работа с null |
---|---|---|---|---|
Treemap | O(log(N)) | Без издержек | В естественном порядке | Недопустимы null ключи, без ограничений на null значения |
HashMap | O(1) | С издержками | Неотсортирован | Допустим null ключ, без ограничений на null значения |
Linked HashMap | O(1) (но медленнее HashMap) | Также как и в HashMap | В порядке добавления | Допустим null ключ, без ограничений на null значения |
При этом следует помнить, что порядок гарантируеутся не всеми реализациями, как например в примере выше.
Стоит помнить, что, как и в случае с итерированием сипсков, если в ходе работы итератора структура данных была изменена (без использования методов итератора), то будет выброшено исключение:
Избежать этого можно удаляя элемент с помощью итератора, с которым происходит работа:
Использование Map в Java
Использование Map в Java
Множество представляет собой набор данных, в котором можно быстро найти существующий элемент. Однако для этого нужно иметь точную копию требуемого элемента. Этот вид поиска не очень распространен, поскольку обычно известна лишь некоторая информация (ключ), по которой можно найти соответствующий элемент. Специально для этого предназначена структура данных,поддерживающая отображение, которую называют также картой. Карта хранит пары «ключ-значение». Каждое значение можно найти по его ключу. Например, в таблице могут находиться записи с информацией о сотрудниках, где ключами являются идентификационные номера сотрудников, а значениями — объекты Employee.
Интерфейс Map (java.util.Map )
Основные реализации Map
В библиотеке Java предусмотрено две основные реализации карт: хэш-карта HashMap и карта-дерево ТгееМар. Оба класса реализуют интерфейс Map.
В хэш-карте ключи расположены случайным образом, а в карте-дереве — в строгом порядке. Хэш-функция, или функция сравнения, применяется только для ключей, а сами значения, соответствующие этим ключам, не хэшируются и не сравниваются.
Какую же из карт следует выбрать? Как и для множеств, хэширование несколько быстрее, поэтому его рекомендуется использовать там, где порядок следования ключей не имеет значения.
Ниже показано, как создается хэш-карта для хранения информации о сотрудниках.
При добавлении объекта к карте должен быть указан и его ключ. В данном случае ключом является строка, а соответствующим значением — объект Employee.
Чтобы обратиться к объекту, нужно воспользоваться ключом.
Если данных, соответствующих указанному ключу в наборе данных нет, метод get() возвращает значение null. Ключи должны быть уникальными: нельзя сохранить два значения с одинаковым ключом. Если вызвать метод put() дважды с одинаковым ключом, то второе значение просто заменит первое. Кроме того, метод put() возвращает предыдущее значение, хранимое с указанным ключом.
Метод remove() удаляет элемент из карты, а метод size() возвращает число элементов карты.
В архитектуре наборов данных карта сама по себе не рассматривается как набор. (В других архитектурах структур данных карта считается набором пар, или значений, индексируемых ключами.) Однако в библиотеке Java предусмотрено использованиепредставления (view) карты, которое реализует интерфейс Collection или один из его дочерних интерфейсов.
Существует три типа представлений: в виде множества ключей, набора значений (который не является множеством) или множества пар «ключ-значение». Ключи и пары «ключ-значение» формируют множество, так как в карте может присутствовать только один уникальный экземпляр объекта-ключа. Перечисленные ниже методы возвращают эти три типа представлений карты.
(Элементы последнего множества пар «ключ-значение» являются объектами внутреннего класса Map.Entry) Обратите внимание, что множество ключей не является объектом HashSet или TreeSet,но представляет собой объект некоторого другого класса, реализующего интерфейс Set.Интерфейс Set расширяет интерфейс Collection.Следовательно, вы можете использовать метод keySet().
Например, можно перебрать все ключи карты:
Если нужно одновременно просматривать ключи и значения, то можно избежать необходимости поиска значений, перечисляя всезаписи. Для этого можно использовать следующую заготовку кода:
Специальные реализации Map
Хэш-карты с нестрогим кэшированием
Класс хэш-карт с нестрогим кэшированием WeakHashMap был разработан для решения интересной задачи. Что происходит со значением, ключ которого больше не используется в программе, например из-за того, что исчезла последняя ссылка на этот ключ? В этом случае обратиться к объекту-значению уже нельзя. А так как этот ключ уже не содержится нигде в программе, то нет никакой возможности удалить его пару «ключ-значение» из карты. Но почему его не может удалить система сборки мусора, в обязанности которой как раз и входит удаление неиспользуемых объектов?
К сожалению, все не так просто. Средство сборки мусора в системе управления памятью следит за действующими объектами. Пока объект карты активен,все ячейки карты также активны. Таким образом, об удалении неиспользуемых значений из активных карт должна позаботиться сама программа. Именно для этого и предназначен класс WeakHashMap, Такая структура данных взаимодействует с системой сборки мусора для удаления тех пар «ключ-значение», для которых единственной ссылкой на ключ является запись в хэш-таблице.
Вот как работает этот механизм. Класс WeakHashMap использует для хранения ключей нестрогие ссылки (weak references). ОбъектWeakReference содержит ссылку на другой объект, т.е. в данном случае на ключ хэш-таблицы. Обычно, если при сборке мусора выясняется, что на некоторый объект нет ссылок, этот объект удаляется. А еслиединственная ссылка на объект имеет типWeakReference, эта нестрогая ссылка помещается в очередь. Периодически происходит проверка на появление новых ссылок в очереди, так как это означает, что данный ключ больше не используется и его объект можно удалить. Таким образом, классWeakHashMap удаляет соответствующее этому ключу значение.
Связанные хэш-карты
В JDK 1.4 были предложены классы LinkedHashSet и LinkedHashMap, которые запоминают последовательность вставки в набор данных новых пунктов. Таким образом, порядок следования пунктов таблицы уже не выглядит случайным. По мере добавления записей в таблицу они формируют двусвязный список.
Рассмотрим, например, карту:
Итератор staff.ketSet().iterator() перечислит ее ключи в следующем порядке:
А итератор staff.values().iterator() перечислит ее значения так:
Связная хэш-карта может запоминать порядок доступа и учитывать его при переборе элементов. Каждый раз, когда вы вызываете метод get() или put() запись, которую он затрагивает, удаляется из занимаемой позиции и перемещается в конец связного списка. При этих операциях изменяется структура связного списка, но не ячеек хэш-таблицы. Запись остается в той ячейке, которая соответствует хэш-коду ключа. Для того чтобы создать подобную хэш-карту, нужно использовать следующее выражение:
Знать порядок доступа необходимо, например, для создания кэша, работающего по принципу «последнего по времени использования». Например, вам может понадобиться хранить в памяти наиболее часто используемые записи, а те, с которыми приходится работать редко, извлекать из базы данных. Если вы не находите запись в таблице, а таблица уже достаточно заполнена, вы можете удалить с помощью итератора первые несколько элементов. Именно эти элементы используются реже других.
Хэш-карты с индивидуальным хэшированнием
В JDK 1.4 добавлен еще один специальный класс IdentityHashMap, выполняющий индивидуальное хэширование. Хэш-коды ключей в нем подсчитываются не методом hashCode(),а методом System.identityHashCode().Этот метод вычисляет хэш-код по адресу объекта в памяти. Кроме того, для сравнения объектов класс IdentityHashMapприменяет оператор ==, а не метод equals().
Иначе говоря, разные объекты считаются отличающимися друг от друга, даже если их содержимое совпадает. Этот класс полезендля реализации алгоритмов обхода объектов (например, для сериализации), в которых требуется следить даже за теми объектами, которые уже были пройдены итератором.
Использование Collections в Map
Немодифицируемые представления
Класс Collectionsсодержит методы, которые создаютнемодифицируемые пред-ставления (unmodifiableview) наборов данных. В этих представлениях реализована проверка существующего набора, выполняемая на этапе работы программы. При по¬пытке модифицировать набор, генерируется исключение и набор данных остается неизменным.
Для получения немодифицируемых Map представлений используются методы:
Предположим, например, что вы хотите, чтобы некоторый фрагмент вашего кода просматривал, но не затрагивал содержимое набора данных. Для этого выполните следующие действия:
МетодCollections.unmodifiableMap возвращает экземпляр класса, реализующего интерфейс Map. Метод доступа этого класса извлекает значения из набора staff.Очевидно, что метод lookAt() может вызывать все методы, объявленные в интерфейсе Map. Однако все модифицирующие методы пере¬определены так, что вместо обращения к базовому набору генерируют исключение UnsupportedOperationException.
Немодифицирующее представление не делает сам набор данных неизменяемым. Вы можете модифицировать набор посредством обычной ссылки (в наше случае это staff).При этом методы, модифицирующие элементы набора, остаются доступными.
Синхронизируемые представления
Если вы обращаетесь к набору данных из нескольких потоков, необходимо при¬нять меры, чтобы не повредить информацию в наборе. Это неминуемо произойдет, если, например, один поток будут пытаться включить элемент в хэш-таблицу, а другой — перегенерировать ее.
Вместо того чтобы реализовать классы наборов данных, обеспечивающих безопасную работу с потоками, разработчики библиотеки предпочли использовать для этого механизм представлений. Например, статический метод synchronizedMap() классаCollectionsможет преобразовать любую карту в Map с синхронизированны¬ми методами доступа.
Теперь вы можете обращаться к объекту map из различных потоков. Такие методы, как get() и put(),сериализованы: каждый метод должен полностью закончить свою работу перед тем, как другой поток сможет вызвать подобный метод.
При разработке программы необходимо следить, чтобы ни один поток не обращался к структуре данных посредством обычных де синхронизированных методов. Самый простой способ обеспечить это — не сохранять ни одной ссылки на базовый объект.
Классы Hashtable и Dictionary
Традиционный класс Hashtable служит той же цели, что и HashMap, и имеет, в сущности, такой же интерфейс. Как и методы класса Vector, методы класс Hashtable синхронизированы. Если Вам не требуется обеспечить синхронизацию или совместимость с кодом для предыдущих версий платформы Java, то в таком случае следует воспользоваться классом HashMap. Класс Dictionary является абстрактным классом-родителем Hashtable.
Если Вам понравилась статья, проголосуйте за нее
Голосов: 31 Голосовать