← Назад к вопросам

Что возвращает HashMap по хеш-коду?

2.0 Middle🔥 131 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что возвращает HashMap по хеш-коду

Прямой ответ

HashMap НЕ возвращает ничего по хеш-коду. Вместо этого, HashMap использует хеш-код как индекс для поиска значения в бакете (bucket). Настоящий ключ и сравнение equals() определяют, какое значение вернуть.

Как работает HashMap

HashMap<String, Integer> map = new HashMap<>();
map.put("Alice", 25);

// Что происходит при put():
// 1. Вычисляем hash code ключа
int hash = "Alice".hashCode();  // например, 2074

// 2. Определяем индекс бакета
int index = hash % capacity;  // например, 2074 % 16 = 14

// 3. В бакете [14] ищем Entry с key.equals("Alice")
// 4. Если найден, обновляем value
// 5. Если не найден, добавляем новый Entry

HashMap.get():

Integer value = map.get("Alice");

// Что происходит при get():
// 1. Вычисляем hash code ключа
int hash = "Alice".hashCode();  // 2074

// 2. Определяем индекс бакета
int index = hash % capacity;  // 14

// 3. В бакете [14] ищем Entry где key.equals("Alice")
// 4. Возвращаем value если найден
// 5. Возвращаем null если не найден

Критическое: hashCode() и equals()

Правило 1: если equals() возвращает true, hashCode() ДОЛЖЕН быть одинаковым

public class User {
    private String id;
    private String name;
    
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof User)) return false;
        User other = (User) obj;
        return this.id.equals(other.id);  // Сравниваем только ID
    }
    
    @Override
    public int hashCode() {
        return id.hashCode();  // Hash ДОЛЖЕН зависеть от ID
    }
}

// Правильно!
User user1 = new User("123", "Alice");
User user2 = new User("123", "Alice");
user1.equals(user2);      // true
user1.hashCode() == user2.hashCode();  // true ✓

// Неправильно!
public int hashCode() {
    return name.hashCode();  // Если name изменится, баг!
}

Проблема 1: Плохая реализация hashCode()

public class BadUser {
    private String id;
    
    @Override
    public int hashCode() {
        return 42;  // Одинаковый hash для всех объектов!
    }
    
    @Override
    public boolean equals(Object obj) {
        return id.equals(((BadUser) obj).id);
    }
}

Map<BadUser, Integer> map = new HashMap<>();
BadUser user1 = new BadUser("1");
BadUser user2 = new BadUser("2");
BadUser user3 = new BadUser("3");

map.put(user1, 10);
map.put(user2, 20);
map.put(user3, 30);

// Все три ключа в одном бакете!
// get() деградирует до O(n) вместо O(1)
// Это называется hash collision

Проблема 2: Изменяемые ключи (mutable keys)

public class MutableKey {
    private String value;
    
    public MutableKey(String value) {
        this.value = value;
    }
    
    public void setValue(String value) {  // Опасно!
        this.value = value;
    }
    
    @Override
    public int hashCode() {
        return value.hashCode();
    }
    
    @Override
    public boolean equals(Object obj) {
        return value.equals(((MutableKey) obj).value);
    }
}

Map<MutableKey, String> map = new HashMap<>();
MutableKey key = new MutableKey("Alice");
map.put(key, "data");  // В бакете hash % capacity = 5

key.setValue("Bob");  // Изменяем ключ!
// Теперь hash % capacity = 7

String data = map.get(key);  // Ищет в бакете 7, но находится в 5!
System.out.println(data);  // null (потеряли данные!)

Что происходит внутри HashMap

Структура данных:

public class HashMap<K, V> {
    
    // Массив бакетов
    private static final int DEFAULT_CAPACITY = 16;
    private Entry<K, V>[] table = new Entry[DEFAULT_CAPACITY];
    
    // Entry для разрешения hash collisions
    static class Entry<K, V> {
        K key;
        V value;
        Entry<K, V> next;  // Linked list для collision
    }
    
    public V put(K key, V value) {
        // 1. Вычислить hash
        int hash = key.hashCode();
        
        // 2. Найти индекс
        int index = hash & (table.length - 1);  // Битовая операция
        
        // 3. Найти Entry в списке (collision resolution)
        Entry<K, V> entry = table[index];
        while (entry != null) {
            if (entry.key.equals(key)) {
                // Ключ найден, обновляем
                V oldValue = entry.value;
                entry.value = value;
                return oldValue;
            }
            entry = entry.next;
        }
        
        // 4. Ключ не найден, добавляем новый
        addEntry(key, value, index);
        return null;
    }
    
    public V get(K key) {
        // 1. Вычислить hash
        int hash = key.hashCode();
        
        // 2. Найти индекс
        int index = hash & (table.length - 1);
        
        // 3. Найти Entry
        Entry<K, V> entry = table[index];
        while (entry != null) {
            if (entry.key.equals(key)) {
                return entry.value;  // ВОЗВРАЩАЕМ ЗНАЧЕНИЕ, НЕ HASH
            }
            entry = entry.next;
        }
        
        return null;  // Ключ не найден
    }
}

Визуализация

HashMap с 4 бакетами и 5 записями:

Index  | Bucket                     | Collision
─────────────────────────────────────────────────
0      | [key=A, value=10]          |
       | [key=E, value=50]          | collision
       |
1      | [key=B, value=20]          |
       |
2      | [key=C, value=30]          |
       | [key=D, value=40]          | collision
       |
3      | (empty)                    |

# Что вернет get("B")?
1. hash("B") = 2
2. index = 2 % 4 = 2
3. Ищем в бакете [2]: [C -> D]
4. C.equals("B")? false
5. D.equals("B")? false
6. Возвращаем null

# Что вернет get("C")?
1. hash("C") = 2
2. index = 2 % 4 = 2
3. Ищем в бакете [2]: [C -> D]
4. C.equals("C")? true
5. Возвращаем value = 30

Hash функция

Для String:

public int hashCode() {
    int h = 0;
    for (int i = 0; i < value.length; i++) {
        h = 31 * h + value[i];
    }
    return h;
}

// "ABC" = A*31^2 + B*31 + C
// Это распределяет значения равномерно

Для custom objects:

public class Point {
    int x, y;
    
    @Override
    public int hashCode() {
        // Правильно: использовать оба поля
        return Objects.hash(x, y);
        
        // Или вручную
        // return 31 * x + y;  // ПЛОХО если y > 31
        // Лучше:
        // return ((long) x << 32) | (y & 0xFFFFFFFFL);
    }
}

Performance

// Хорошая hash function (равномерное распределение)
HashMap<String, Integer> map = new HashMap<>();
map.put("key1", 1);  // O(1)
map.put("key2", 2);  // O(1)
map.get("key1");     // O(1)

// Плохая hash function (много collisions)
HashMap<BadKey, Integer> map = new HashMap<>();
map.put(new BadKey(1), 1);    // O(1) первая
map.put(new BadKey(2), 2);    // O(n) в collision chain
map.get(new BadKey(1));       // O(n) ищет в цепи

// Java 8+: при много collisions -> Red-Black Tree
// Если в одном бакете > 8 элементов, переходим на дерево O(log n)

Правильная реализация equals() и hashCode()

public class Employee {
    private String id;
    private String name;
    private int age;
    
    @Override
    public int hashCode() {
        return Objects.hash(id);  // Используем ВСЕ поля из equals
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        
        Employee other = (Employee) obj;
        return Objects.equals(id, other.id);
    }
}

// Правило: если две операции используют одни поля в equals,
// они ДОЛЖНЫ использовать одни же поля в hashCode

WeakHashMap и IdentityHashMap

// WeakHashMap
// Ключи могут быть удалены GC если больше ничего на них не ссылается
Map<String, Integer> weakMap = new WeakHashMap<>();
weakMap.put("key", 100);
// Если никто не ссылается на "key", GC может удалить

// IdentityHashMap
// Использует System.identityHashCode, == вместо equals
Map<String, Integer> idMap = new IdentityHashMap<>();
String s1 = new String("key");
String s2 = new String("key");
idMap.put(s1, 100);
idMap.get(s2);  // null! (разные объекты)

Вывод

HashMap по хеш-коду НЕ возвращает:

  1. Возвращает значение — map.get(key) возвращает V, не hash code
  2. Использует hash как индекс — index = hash & (capacity - 1)
  3. Ищет по equals() — в бакете сравниваются ключи с equals()
  4. Требует согласованности — если equals() говорит equal, то hashCode() должен быть одинаковым
  5. Обрабатывает collision — linked list или red-black tree в Java 8+

Критическое правило: НИКОГДА не используй изменяемые объекты как ключи HashMap, потому что изменение ключа нарушает инвариант hash code.

Что возвращает HashMap по хеш-коду? | PrepBro