Проект

Общее

Профиль

HashMap

Обзор

В этой статье мы увидим, как использовать HashMap в Java, и посмотрим, как это работает внутри.

Класс, очень похожий на HashMap - это Hashtable.

Основное использование

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

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

Преимущество HashMap заключается в том, что временная сложность вставки и извлечения значения составляет в среднем O(1) . Мы рассмотрим, как этого можно достичь позже. Давайте сначала посмотрим, как использовать HashMap.

Настраивать

Давайте создадим простой класс, который будем использовать на протяжении всей статьи:

public class Product {

    private String name;
    private String description;
    private List<String> tags;

    // standard getters/setters/constructors

    public Product addTagsOfOtherProduct(Product product) {
        this.tags.addAll(product.getTags());
        return this;
    }
}

Помещать

Теперь мы можем создать HashMap с ключом типа String и элементами типа Product :

Map<String, Product> productsByName = new HashMap<>();

И добавляем товары в наш HashMap :

Product eBike = new Product("E-Bike", "A bike with a battery");
Product roadBike = new Product("Road bike", "A bike for competition");
productsByName.put(eBike.getName(), eBike);
productsByName.put(roadBike.getName(), roadBike);

Получить

Мы можем получить значение из карты по ее ключу:

Product nextPurchase = productsByName.get("E-Bike");
assertEquals("A bike with a battery", nextPurchase.getDescription());

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

Product nextPurchase = productsByName.get("Car");
assertNull(nextPurchase);

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

Product newEBike = new Product("E-Bike", "A bike with a better battery");
productsByName.put(newEBike.getName(), newEBike);
assertEquals("A bike with a better battery", productsByName.get("E-Bike").getDescription());

Нуль как ключ

HashMap также позволяет нам использовать null в качестве ключа:

Product defaultProduct = new Product("Chocolate", "At least buy chocolate");
productsByName.put(null, defaultProduct);

Product nextPurchase = productsByName.get(null);
assertEquals("At least buy chocolate", nextPurchase.getDescription());

Значения с одним и тем же ключом

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

productsByName.put(defaultProduct.getName(), defaultProduct);
assertSame(productsByName.get(null), productsByName.get("Chocolate"));

Удалить значение

Мы можем удалить сопоставление ключ-значение из HashMap :

productsByName.remove("E-Bike");
assertNull(productsByName.get("E-Bike"));

Проверьте, существует ли ключ или значение на карте

Чтобы проверить, присутствует ли ключ на карте, мы можем использовать метод containsKey() :

productsByName.containsKey("E-Bike");

Или, чтобы проверить, присутствует ли значение на карте, мы можем использовать метод containsValue() :

productsByName.containsValue(eBike);

В нашем примере вызовы обоих методов вернут true . Хотя они выглядят очень похожими, между вызовами этих двух методов есть важная разница в производительности. Сложность проверки существования ключа составляет O(1) , а сложность проверки элемента — O(n), так как необходимо перебрать все элементы на карте.

Итерация по HashMap

Существует три основных способа перебора всех пар ключ-значение в HashMap .

Мы можем перебрать набор всех ключей:

for(String key : productsByName.keySet()) {
    Product product = productsByName.get(key);
}

Или мы можем перебрать набор всех записей:

for(Map.Entry<String, Product> entry : productsByName.entrySet()) {
    Product product =  entry.getValue();
    String key = entry.getKey();
    //do something with the key and value
}

Наконец, мы можем перебрать все значения:

List<Product> products = new ArrayList<>(productsByName.values());

Ключ

Мы можем использовать любой класс в качестве ключа в нашей HashMap . Однако, чтобы карта работала правильно, нам нужно предоставить реализацию для equals() и hashCode(). Допустим, мы хотим иметь карту с продуктом в качестве ключа и ценой в качестве значения:

ashMap<Product, Integer> priceByProduct = new HashMap<>();
priceByProduct.put(eBike, 900);

Давайте реализуем методы equals() и hashCode() :

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }

    Product product = (Product) o;
    return Objects.equals(name, product.name) &&
      Objects.equals(description, product.description);
}

@Override
public int hashCode() {
    return Objects.hash(name, description);
}

Обратите внимание, что hashCode() и equals() необходимо переопределять только для классов, которые мы хотим использовать в качестве ключей карты, а не для классов, которые используются только как значения в карте. Мы увидим, почему это необходимо, в разделе 5 этой статьи.

Дополнительные методы начиная с Java 8

Java 8 добавила в HashMap несколько методов функционального стиля . В этом разделе мы рассмотрим некоторые из этих методов. Для каждого метода мы рассмотрим два примера. В первом примере показано, как использовать новый метод, а во втором примере показано, как добиться того же в более ранних версиях Java. Поскольку эти методы довольно просты, мы не будем рассматривать более подробные примеры.

для каждого()

productsByName.forEach( (key, product) -> {
    System.out.println("Key: " + key + " Product:" + product.getDescription());
    //do something with the key and value
});

До Java 8:

for(Map.Entry<String, Product> entry : productsByName.entrySet()) {
    Product product =  entry.getValue();
    String key = entry.getKey();
    //do something with the key and value
}

В статье «Руководство по Java 8 forEach» цикл forEach рассматривается более подробно.

получить или по умолчанию ()

Используя метод getOrDefault() , мы можем получить значение из карты или вернуть элемент по умолчанию, если для данного ключа нет сопоставления:

Product chocolate = new Product("chocolate", "something sweet");
Product defaultProduct = productsByName.getOrDefault("horse carriage", chocolate); 
Product bike = productsByName.getOrDefault("E-Bike", chocolate);

До Java 8:

Product bike2 = productsByName.containsKey("E-Bike") 
    ? productsByName.get("E-Bike") 
    : chocolate;
Product defaultProduct2 = productsByName.containsKey("horse carriage") 
    ? productsByName.get("horse carriage") 
    : chocolate;

поставитьеслиотсутствует()

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

productsByName.putIfAbsent("E-Bike", chocolate);

До Java 8:

if(productsByName.containsKey("E-Bike")) {
    productsByName.put("E-Bike", chocolate);
}

Links