Интерфейс Map, его реализации, примеры

В этом уроке мы продолжаем рассматривать тему Collection Framework, а именно - интерфейс Map, а также его классы в Java.

  1. Что такое отображение?
  2. Интерфейс Мар.Entry
  3. Методы интерфейса Map
  4. Класс HashMap
  5. Класс TreeMap
  6. Класс LinkedHashMap
  7. Интерфейс SortedMap
  8. Интерфейс NavigableMap
  9. Свойства ключей интерфейса Map
  10. Summary

1. Что такое отображение?

Итак, что же такое Map? Map представляет собой пару объектов - ключ и значение. Другие названия Map - это карта, отображение, а также такой англицизм как мапа. По заданному ключу можно найти его значение.

Ключи и значения являются объектами. Ключи должны быть уникальными, а значения могут быть дублированными.

В одних отображениях допускаются null ключи и null значения, а в других - они не допускаются.

Уникальность ключей определяет реализация метода equals().

Интерфейс Map является обобщённым и записывается он таким образом:

interface Мар<К, V>

сначала мы указываем K - это тип ключей, а потом мы указываем V - это тип хранимых значений.

Пример объявления Map:

Map<String, Double>

Допустим String - это ключи, Double - это значение. 

Либо такой вариант:

Map<Number, Person>

здесь Number - это ключи, а Person - это значение.

Давайте рассмотрим пример использования Map для хранения зарплаты сотрудников. Допустим у нас с вами есть список сотрудников и для каждого из них нам нужно сохранить зарплату:

Map<String, Double> hashMap = new HashMap<>();

hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

System.out.println(hashMap.get("Иванов"));

В этом коде мы объявляем переменную hashMap, указываем обобщённые типы String и Double. String - это будет ключ, Double - это значение. И с помощью метода put() мы добавляем туда пару значений. То есть у Иванова зарплата - 3434.34, у Петрова - 123.22, у Сидорова - 1378.00. И потом для того, чтобы получить нам зарплату Иванова, мы с помощью метода get() это выполняем.

Давайте рассмотрим структуру интерфейса Map, которая изображена на картинке ниже. Интерфейс Map входит в Collection Framework, но явно не наследует интерфейс Collection, как например другие интерфейсы Collection Framework такие, как List, Set или Queue. Основные классы, которые реализуют интерфейс Map - это классы HashMapLinkedHashMap и TreeMap, а также класс Hashtable. Класс Hashtable является legacy и не очень часто сейчас используется.

Другие интерфейсы, которые расширяют интерфейс Map - это интерфейсы SortedMap и интерфейс NavigableMap.

Сразу скажу, что это не все классы интерфейса Map. Но это те классы, которые наиболее часто используются и в этом уроке мы рассмотрим только их.

Иерархия отображений фото

2. Интерфейс Мар.Entry

Интерфейс Мар.Entry описывает элемент карты, то есть пару ключ и значение. Мар.Entry - это вложенный интерфейс интерфейса Мар. Это, кстати, хороший пример вложенного интерфейса. Его использование мы рассмотрим чуть ниже в методах интерфейса Map

3. Методы интерфейса Map

Метод put()

V put(К key, V value)

Он добавляет элемент в Map, перезаписывая любое предшествующее значение. Сначала мы передаем key, потом мы передаем value.

Пример его использования:

Map<String, Double> hashMap = new HashMap<>();

hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

В Map мы можем добавлять только объекты. Но в этом примере добавляются не объекты, а значения типа Double. Просто при передаче примитивных значений будет происходить autoboxing - создастся объект класса Double.

При повторном вызове метода put(), с уже существующим ключом, предыдущее значение будет перезаписано. Допустим в следующем примере добавляются три значения - Иванов, Петров и Сидоров. А потом повторно вызывается метод put(), куда опять добавляется ключ Иванов, но  значение совсем другое:

Map<String, Double> hashMap = new HashMap<>();

hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

hashMap.put("Иванов", 2222.00);

System.out.println(hashMap);

И в результате выполнения программы, мы видим, что значение Иванов у нас 2222. То есть оно перезаписалось:

{Иванов=2222.0, Сидоров=1378.0, Петров=123.22}

Метод putAll()

void putAll(Мар<? extends К, ? extends V> map)

На вход этого метода мы можем передать другой map и этот метод помещает все значения из map в вызывающую карту.

Давайте рассмотрим пример использования метода putAll():

public class PutAllExample {
    public static void main(String[] args) {
        Map<Number, Box> smallBoxes = new HashMap<>();
        smallBoxes.put(1, new Box(1, 1, 1));
        smallBoxes.put(2, new Box(3, 4, 5));
        smallBoxes.put(3, new Box(6, 8, 9));

        Map<Number, Box> bigBoxes = new HashMap<>();
        bigBoxes.put(4, new Box(100, 200, 300));
        bigBoxes.put(5, new Box(500, 500, 500));

        bigBoxes.putAll(smallBoxes);

        System.out.println(bigBoxes);
    }
}

В этом примере мы создаём две мапы - smallBoxes и bigBoxes. Ключом будет Number - это допустим какой-то идентификатор Box. В нашем случае это будет 1 2 3 4 5. И Box - это какой-то класс, у которого есть переменные width, heigth и depth, определяющие его структуру. В переменную smallBoxes мы добавляем маленькие ящики, у которых маленькая ширина и маленькие высота и глубина. В bigBoxes мы добавляем соответственно большие ящики. А потом мы хотим в bigBoxes добавить все ящики из smallBoxes. Это мы делаем с помощью метода putAll(). В конце мы выводим на консоль содержимое bigBoxes и видим, что да - в bigBoxes добавились все ящики из smallBoxes:

{1=Box{width=1.0, height=1.0, depth=1.0}, 
2=Box{width=3.0, height=4.0, depth=5.0}, 
3=Box{width=6.0, height=8.0, depth=9.0}, 4=Box{width=100.0, height=200.0, depth=300.0}, 
5=Box{width=500.0, height=500.0, depth=500.0}}

Метод get()

V get(Object key)

С помощью метода get() мы можем вернуть значение ассоциированное с ключом key. В метод get() мы передаём key и получаем его значение. Метод возвращает null, если такой ключ не найден в нашей мапе.

Пример использования метода get():

Map<String, Double> hashMap = new HashMap<>();
hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

System.out.println(hashMap.get("Петров"));

Для того чтобы получить зарплату Петрова, мы передаём в метод get() наш ключ Петров. И в результате выполнения программы мы видим его зарплату:

123.22

Метод getOrDefault()

V getOrDefault(Object key, V defaultValue) 

Метод возвращает значение, ассоциированное с ключом key, точно также, как и предыдущий метод get(). Но он может вернуть значение defaultValue, если такой ключ в map не найден. Это очень удобно, если у вас такого значения нет, но вы не хотите получить null в результате. Вы хотите получить какое-то другое значение допустим, 0, empty или что-то ещё. И пример использования этого метода:

Map<String, Double> hashMap = new HashMap<>();

hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

System.out.println(hashMap.getOrDefault("Харитонов", 0.0));

Результат выполнения программы:

0.0

Метод containsKey() 

boolean containsKey(Object key)

Метод containsKey() возвращает true, если вызывающий map содержит такой ключ, в противном случае возвращается значение false.

Рассмотрим пример:

Map<String, Double> hashMap = new HashMap<>();

hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

System.out.println(hashMap.containsKey("Сидоров"));
System.out.println(hashMap.containsKey("Сидорова"));

Здесь мы проверяем, а есть ли в нашей мапе ключ Сидоров? И нам возвращается значение true - да он есть. Есть ли в нашей мапе значение Сидорова? Такого значения в нашей мапе нет - возвращается значение false.

Результат выполнения программы:

true
false

Метод containsValue()

boolean containsValue(Object value)

Метод containsValue() делает то же самое, что и предыдущий метод containsKey(), но проверяет значения, а не ключи.

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

Пример:

Map<String, Double> hashMap = new HashMap<>();

hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

System.out.println(hashMap.containsValue(123.22));
System.out.println(hashMap.containsValue(123));

В этом коде проверяется, а есть ли в мапе значение 123.22 и значения 123.

Результат выполнения программы:

true
false

Метод remove()

V remove(Object key)

Метод удаляет элемент, чей ключ равен key.

Допустим, мы хотим удалить из нашей мапы пару с ключом Иванов:

Map<String, Double> hashMap = new HashMap<>();

hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

hashMap.remove("Иванов");
System.out.println(hashMap);

Вызываем метод remove, передаём ключ Иванов, и в результате выполнения программы мы видим, что да наша мапа не содержит Иванова:

{Сидоров=1378.0, Петров=123.22}

Метод size()

int size()

Метод size() возвращает число пар ключ значений в карте. Этот метод работает точно так же, как и метод size() в остальных классах коллекции. Пример использования этого метода:

Map<String, Double> hashMap = new HashMap<>();

hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

System.out.println("Количество элементов: "
                + hashMap.size());

 Результат выполнения программы:

Количество элементов 3

Метод clear()

void clear()

Метод clear() удаляет все пары ключ-значения из вызывающей карты, то есть полностью очищает наш Map. В следующем примере вызывается метод clear(), после чего - метод size():

Map<String, Double> hashMap = new HashMap<>();

hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

hashMap.clear();

System.out.println("Количество элементов: "
                + hashMap.size());

 и мы видим, что количество элементов равно 0:

Количество элементов 0

Метод replace()

V replace(K key, V value)

Метод replace() заменяет значение value для ключа key. Если такого ключа нет, значение не добавляется. В этом и состоит отличие метода replace от метода put. Допустим, мы хотим заменить значение для ключа Иванов. В следующем пример вызываем метод replace:

Map<String, Double> hashMap = new HashMap<>();

hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

hashMap.replace("Иванов", 4000.0);
hashMap.replace("Иванова", 4000.0);

System.out.println(hashMap);

И в результате программы мы видим, что значение для Иванова у нас перезаписалось с 3434 на 4000. Потом мы хотим изменить значение для ключа Иванова, но такого ключа у нас в мапе не было, а поэтому это значение просто не будет добавлено:

{Иванов=4000.0, Сидоров=1378.0, Петров=123.22}

Метод isEmpty()

boolean isEmpty()

Метод isEmpty() проверяет, пуста ли вызывающая карта. Возвращает true, если вызывающая карта пуста. В противном случае возвращает значение false.

Пример: допустим мы создали наш Map и сразу же вызвали метод isEmpty():

Map<String, Double> hashMap = new HashMap<>();

System.out.println(hashMap.isEmpty());

hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

System.out.println(hashMap.isEmpty());

В результате выполнения программы мы видим true, то есть да - наш Map ещё пуст:

true
false

После того как мы добавили какие-то значения, мы ещё раз вызываем этот метод isEmpty и видим в результате выполнения программы false. То есть, теперь мапа не пуста.

Метод keySet()

Set<K> keySet()

Метод keySet() возвращает Set, который содержит только ключи вызывающие карты. Это удобно, для того, чтобы сделать перебор элементов коллекции. Это мы можно сделать так - вызывается метод keySet(), возвращаем Set. Причём, обратите внимание, что обобщённый тип для нашего Set такой же, как и у ключей первоначальной мапы. После этого в цикле for each перебираем наши ключи и выводим на консоль:

Map<String, Double> hashMap = new HashMap<>();

hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

Set<String> keys = hashMap.keySet();
for (String key : keys) {
     System.out.print("Ключ: " + key);
}

Результат выполнения программы:

Ключ: Иванов
Ключ: Сидоров
Ключ: Петров

Метод values()

Collection<V> values()

Метод values() возвращает коллекцию, которая содержит только значение карты. Давайте посмотрим пример:

Map<String, Double> hashMap = new HashMap<>();

hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

Collection<Double> values = hashMap.values();
for (Double value : values) {
     System.out.print("Значение: " + value);
}

Мы вызвали метод values(), вернули только ключи нашей коллекции. Причём, обратите внимание, что тип здесь - Collection, а не Set, как в предыдущем случае. Мы перебираем значение нашей коллекции и выводим на консоль. Этот метод тоже удобно использовать для перебора значений коллекции.

Результат выполнения программы:

Значение: 3434.34
Значение: 1378.0
Значение: 123.22

Метод entrySet()

Set<Map. Entry<K, V> entrySet()

Метод entrySet() возвращает Set, содержащий все значения объектов типа Map.Entry

Давайте рассмотрим пример использования этого метода:

Map<String, Double> hashMap = new HashMap<>();

hashMap.put("Иванов", 3434.34);
hashMap.put("Петров", 123.22);
hashMap.put("Сидоров", 1378.00);

Set<Map.Entry<String, Double>> entries = hashMap.entrySet();
for (Map.Entry<String, Double> entry : entries) {
     System.out.print("Ключ: " + entry.getKey());
     System.out.println(" Значение: " + entry.getValue());
}

Для нашей мапы мы вызываем этот метод и получаем набор, который содержит Map.Entry. И Map.Entry имеет такой же обобщённый тип, как и наша первоначальная мапа - String и Double. И дальше мы можем в цикле for перебирать значения нашего набора. Каждый элемент в цикле for будет иметь значение Map.Entry. И дальше выводим на консоль ключ и значение для этого ключа:

Ключ: Иванов Значение: 3434.34
Ключ: Сидоров Значение: 1378.0
Ключ: Петров Значение: 123.22

4. Класс HashMap

Класс HashMap реализует интерфейс Мар. Он использует хеш-таблицу для хранения карты. Это позволяет обеспечить константное время выполнения методов get() и put() даже при больших наборах.

Ключи и значения могут быть любых типов, в том числе и null.

Класс HashMap используется наиболее часто.

HashMap обобщенный класс со следующим объявлением: 

class HashMap<K, V>

Пример использования класса HashMap

Здесь мы объявляем переменную hashMap и в качестве класса используем именно класс HashMap:

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class HashMapExample{
    public static void main(String[] args) {
        Map<String, Double> hashMap = new HashMap<>();

        hashMap.put("Иванов", 3434.34);
        hashMap.put("Петров", 123.22);
        hashMap.put("Сидоров", 1378.00);

        Set<String> keys = hashMap.keySet();

        for (String key : keys) {
            System.out.print(key + ": ");
            System.out.println(hashMap.get(key));
        }
    }
}

Результат выполнения программы:

Иванов: 3434.34
Сидоров: 1378.0
Петров: 123.22

Обратите внимание, что порядок вставки элементов не сохраняется.

5. Класс TreeMap

TreeMap – хранит элементы в порядке сортировки. TreeMap сортирует элементы по возрастанию от первого к последнему. Порядок сортировки может задаваться реализацией интерфейсов Comparator и Comparable. Реализация Comparator передается в конструктор TreeMapComparable используется при добавлении элемента в карту.

Если мы посмотрим на его структуру, мы увидим что TreeMap на самом деле реализует интерфейс NavigableMap и SortedMap.

Конструкторы класса TreeMap

Давайте рассмотрим конструкторы класса TreeMap:

 Первый вариант самый простой - мы не передаём значение, мы создаём пустой TreeMap:

TreeMap()

Второй вариант - мы передаём Comparator, который будет каким-то другим образом сортировать элементы коллекции:

TreeMap(Comparator<? super К> comparator) 

Третий и четвертый варианты - мы можем передать какую-то другую мапу либо отсортированную мапу:

TreeMap(Map<? extends К, ? extends V> map) 
TreeMap(SortedМap<K, ? extends V> sortedMap) 

Пример использования класса TreeMap

import java.util.SortedMap;
import java.util.TreeMap;

public class TreeMapExample {
    public static void main(String[] args) {
        SortedMap<String, Double> treeMap = new TreeMap<>();

        treeMap.put("Иванов", 3434.34);
        treeMap.put("Петров", 123.22);
        treeMap.put("Сидоров", 1378.00);

        treeMap.forEach((key, value) -> System.out.println(key + ": " + value));
    }
}

Результат выполнения программы:

Иванов: 3434.34
Петров: 123.22
Сидоров: 1378.0

Ключи отсортированы по возрастанию.

6. Класс LinkedHashMap

Класс LinkedHashMap расширяет HashMap. Он создает связный список элементов в карте, расположенных в том порядке, в котором они вставлялись. Другими словами класс LinkedHashMap нужен тогда, когда вам нужно сохранить порядок вставки элементов.

Давайте рассмотрим пример использования класса LinkedHashMap. Смотрите, мы создаём переменную linkedHashMap, объявляем её с помощью интерфейса Map и класс который мы используем - это LinkedHashMap. Дальше мы добавляем туда несколько элементов и дальше в цикле for each выводим их на консоль.

public class LinkedHashMapExample {
    public static void main(String[] args) {
        Map<String, Double> linkedHashMap = new LinkedHashMap<>();

        linkedHashMap.put("Петров", 123.22);
        linkedHashMap.put("Сидоров", 1378.00);
        linkedHashMap.put("Иванов", 3434.34);

        linkedHashMap.forEach((key, value) -> System.out.println(key + ": " + value));
    }
}

И мы видим, что порядок вставки у нас сохраняется:

Петров: 123.22
Сидоров: 1378.0
Иванов: 3434.34

7. Интерфейс SortedMap

Интерфейс SortedMap расширяет Мар. Он гарантирует, что элементы размещаются в возрастающем порядке значений ключей. Класс TreeMap реализует этот интерфейс.

Методы интерфейса SortedMap

Методы firstKey() и lastKey()

К firstKey()

Метод firstKey() возвращает первый ключ вызывающей карты.

К lastKey()

 Метод lastKey() возвращает последний ключ вызывающей карты.

Давайте рассмотрим пример использования этих методов:

SortedMap<Integer, Double> treeMap = new TreeMap<>();

treeMap.put(3, 3434.34);
treeMap.put(2, 1378.00);
treeMap.put(1, 123.22);
treeMap.put(100, 123.22);

System.out.println(treeMap.firstKey());
System.out.println(treeMap.lastKey());

Мы добавляем элементы в нашу карту. В этом случае у нас ключ Integer, а значение Double. Ключами у нас будут просто какие-то идентификаторы 1 2 3 и так далее. И смотрите - когда мы вызываем метод firstKey(), у нас возвращается значение 1, и когда мы вызываем метод lastKey() у нас возвращается значение 100:

1
100

Методы headMap(), subMap(), tailMap()

SortedMap<K, V> headMap(К end)
SortedMap<K, V> subMap(К start, К end)
SortedMap<K, V> tailMap (К start)

Эти методы позволяют вырезать какую-то часть из Map. На вход этих методов передаются ключи. Давайте рассмотрим на примере, что делают эти методы:

SortedMap<Integer, Double> treeMap = new TreeMap<>();

treeMap.put(3, 3434.34);
treeMap.put(2, 1378.00);
treeMap.put(1, 123.22);
treeMap.put(4, 444.00);
treeMap.put(5, 555.00);
treeMap.put(100, 123.22);

System.out.println("treeMap: " + treeMap);
System.out.println("headMap: " + treeMap.headMap(3));
System.out.println("subMap: " + treeMap.subMap(3, 5));
System.out.println("tailMap: " + treeMap.tailMap(4));

Здесь создается переменная treeMap с типом SortedMap, и добавляются некоторые значения. Сначала просто выведится на консоль содержимое treeMap. И мы видим, что значения отсортировались по возрастанию наших ключей - ключ 1 2 3 4 5 и 100.

Дальше вызывается метод headMap() со значением 3. Значение 3 - это у нас ключ. С помощью метода headMap() мы вырезаем часть коллекции от  начала и до ключа 3. В результате мы видим, что headMap содержит значения с ключами 1 и 2. Значение toKey, которое передается на вход метода является exclusive. То есть этот ключ не будет включён в результирующую карту.

Следующий метод subMap() вырезает кусочек карты из середины - от ключа 3 ключа 5. В результате будет карта со значениями 3 и 4. Причём, смотрите - когда мы передаём здесь ключи, то первое значение будет inclusive, оно будет входить в карту. Последнее - exclusive.

И метод tailMap() - он вырезает значение от ключика 4 и до конца. То есть, вырезает хвостик. Tail - это хвост. Опять же, значение fromKey будет inclusive, оно будет включаться в результирующую карту. В результате мы видим ключи 4 5 и 100:

treeMap: {1=123.22, 2=1378.0, 3=3434.34, 4=444.0, 5=555.0, 100=123.22}
headMap: {1=123.22, 2=1378.0}
subMap: {3=3434.34, 4=444.0}
tailMap: {4=444.0, 5=555.0, 100=123.22}

Интерфейс NavigableMap был добавлен в Java 6. Он расширяет SortedMap и добавляет некоторые свои методы, которые мы сейчас рассмотрим. Класс TreeMap реализует интерфейс NavigableMap.

Методы интерфейса NavigableMap

Методы lowerKey(), floorKey(), higherKey(), ceilingKey()

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

K lowerKey(K key)
K floorKey(K key)
K higherKey(K key)
K ceilingKey(K key)

Тоже, давайте рассмотрим использование этих методов на примере:

NavigableMap<Integer, Double> navigableMap = new TreeMap<>();

navigableMap.put(3, 3434.34);
navigableMap.put(2, 1378.00);
navigableMap.put(1, 123.22);
navigableMap.put(4, 444.00);
navigableMap.put(5, 555.00);
navigableMap.put(100, 123.22);

System.out.println("lowerKey: " + navigableMap.lowerKey(4));
System.out.println("floorKey: " + navigableMap.floorKey(4));
System.out.println("higherKey: " + navigableMap.higherKey(4));
System.out.println("ceilingKey: " + navigableMap.ceilingKey(4));

Итак, опять же создаётся Map. Переменная navigableMap имеет теперь тип NavigableMap. Ключи - Integer и Double, класс - это TreeMap. На вход всех методов передаем значение 4.

Метод lowerKey() возвращает значение 3. То есть метод lowerKey() возвращает нам ключ, который строго меньше чем тот, который мы передали - строго меньше чем 4.

Метод floorKey() делает то же самое, что и lowerKey(), но равенство здесь нестрогое. То есть, мы ищем ключ, который будет меньше либо равен ключу, который мы передаём.

Метод higherKey() возвращает значение 5, то есть метод возвращает значение строго больше 4.

И метод ceilingKey() возвращает значение 4 -значение большее либо равное данному ключу. Здесь используется нестрогое равенство:

lowerKey: 3
floorKey: 4
higherKey: 5
ceilingKey: 4

Методы lowerEntry(), floorEntry(), higherEntry(), ceilingEntry()

Map.Entry<K,V> lowerEntry(K key) 
Map.Entry<K,V> floorEntry(K key)
Map.Entry<K,V> higherEntry(K key)
Map.Entry<K,V> ceilingEntry(K key)

Методы позволяют получить соответственно меньший, меньше или равный, больший, больше или равную пару “ключ-значение” по отношению к заданному.

Эти методы делают то же самое, что и предыдущие методы lowerKey(), floorKey(), higherKey(), ceilingKey(), но только они возвращают не значения, а возвращают пару значений (Entry), то есть Map.Entry - ключ и значение.

Методы pollFirstEntry, pollLastEntry, firstEntry и lastEntry

Map.Entry<K,V> pollFirstEntry()
Map.Entry<K,V> pollLastEntry()
Map.Entry<K,V> firstEntry()
Map.Entry<K,V> lastEntry()

Методы pollFirstEntry и pollLastEntry возвращают соответственно первый и последний элементы карты, удаляя их из коллекции. Методы firstEntry и lastEntry также возвращают соответствующие элементы, но без удаления.

Давайте рассмотрим использование этих методов на примере:

NavigableMap<Integer, Double> navigableMap = new TreeMap<>();

navigableMap.put(3, 3434.34);
navigableMap.put(2, 1378.00);
navigableMap.put(1, 123.22);
navigableMap.put(4, 444.00);
navigableMap.put(5, 555.00);
navigableMap.put(100, 123.22);

System.out.println("navigableMap: " + navigableMap);
System.out.println("firstEntry: " + navigableMap.firstEntry());
System.out.println("pollFirstEntry: " + navigableMap.pollFirstEntry());
System.out.println("lastEntry: " + navigableMap.lastEntry());
System.out.println("pollLastEntry: " + navigableMap.pollLastEntry());

System.out.println("navigableMap: " + navigableMap);

Мы создаём переменную navigableMap, объявляем её с типом NavigableMap, ключ у нас типа Integer, значение типа Double. Добавляем в этот map какие-то значения и сначала просто выводим на консоль. Ключи будут отсортированы:

navigableMap: {1=123.22, 2=1378.0, 3=3434.34, 4=444.0, 5=555.0, 100=123.22}
firstEntry: 1=123.22
pollFirstEntry: 1=123.22
lastEntry: 100=123.22
pollLastEntry: 100=123.22
navigableMap: {2=1378.0, 3=3434.34, 4=444.0, 5=555.0}

Дальше вызывается метод firstEntry(), который возвращает первую пару - ключ 1 и его значение.

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

Дальше вызываем методы lastEntry() и по pollLastEntry(), которые возвращают нам одинаковые значения с ключом 100. Метод lastEntry() просто возвращает, а pollLastEntry() возвращает значение и удаляет его. И мы видим, что в последней строчке вывода нет ключа со значением 100.

Методы subMap(), headMap() и tailMap()

NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)
NavigableMap<K,V> headMap(K toKey, boolean inclusive)
NavigableMap<K,V> tailMap(K fromKey, boolean inclusive)

Следующие методы интерфейса NavigableMap - это subMap(), headMap() и tailMap(). Посмотрите - это такие же методы, какие были и в интерфейсе SortedMap. Отличие в том, что здесь добавляются переменные fromInclusive и toInclusive. Зачем они нужны? Вы можете с помощью этих параметров указать, а является ли ключ inclusive, передавая значение true. Если же вы хотите, чтобы он был exclusive, вы передаёте значение false.

9. Свойства ключей интерфейса Map

И последний пункт, который мы рассмотрим в этом уроке - это ключ интерфейса Map.

Мы уже видели, что ключ - это достаточно важная часть интерфейса Map. Поэтому давайте более детально рассмотрим его характеристики - каким он должен быть:

  • Итак, для корректной работы с картами Вы должны переопределять обязательно методы equals() и hashCode() для класса ключей.
  • Конечно же, допускается добавление объектов без переопределения этих методов, но найти потом эти объекты в Map вы не сможете.
  • В качестве ключа чаще всего используются неизменяемые (immutable) объекты. Например, объекты класса String и класса Integer.
  • Конечно же, вы можете использовать в качестве ключа и изменяемые (mutable) объекты. Но здесь есть одно но - есть вероятность, что такие объекты будут изменены и тогда вы не сможете по этому ключу найти нужное вам значение.

Давайте рассмотрим это на примере:

Map<Product, String> map = new HashMap<>();
Product doll = new Product("Кукла", 534, "Украина");
Product box = new Product("Кубик", 34, "Украина");
Product car = new Product("Машинка", 200, "Украина");

map.put(doll, "Антошка");
map.put(box, "Антошка");
map.put(car, "Детский мир");

System.out.println(map.get(doll));
doll.setCost(434);

System.out.println(map.get(doll));

В этом коде создается Map, класс - HashMap, ключ - тип Product, значение типа String. Product - это класс, с переменными name, cost и manufacturer. Создаются три объекта класса Product и добавляются в карту в качестве ключей. Потом вызывается метод get(), с переменной doll. И в результате программы, я вижу, что да - вернулось значение Антошка, то, которое мы передавали. То есть пока всё работает хорошо.

Антошка
null

Но потом в программе вызывается метод setCost() и устанавливается новое значение для cost. И теперь, при вызове метода get(doll), возвращается значение null. Это происходит потому, что ключ изменён, и найти значение теперь невозможно.

Очень часто ключом является какая-то объекта значения. Допустим пусть Product теперь является значением, а в качестве ключа можно использовать его name - кукла, кубик и машинка:

Map<String, Product> map = new HashMap<>();
Product doll = new Product("Кукла", 534, "Украина");
Product box = new Product("Кубик", 34, "Украина");
Product car = new Product("Машинка", 200, "Украина");

map.put("Кукла", doll);
map.put("Кубик", box);
map.put("Машинка", car);

10. Summary

  • Используйте интерфейс Map для хранения пар ключ-значения.
  • Используйте класс HashMap, если порядок размещения элементов не важен.
  • Используйте класс TreeMap, если существует необходимость в сортировке ключей.
  • Используйте класс LinkedHashMap для сохранения порядка вставки элементов.
  • В качестве ключа лучше всего использовать неизменяемые (immutable) объекты, такие как классы String или Integer.
  • В качестве ключа можно использовать переменную объекта значения.
Читайте также:
Trustpilot
Trustpilot
Комментарии