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¶