Object.hashCode(): примеры (JAVA)

hashCode в Java: принципы и примеры использования
Раздел: Объекты
Object.hashCode(): int

Общее описание и контракт метода

Метод Object.hashCode() в Java имеет сигнатуру public int hashCode(). Аргументов не принимает. Возвращаемое значение - целое число 32‑битного типа int, которое служит хеш-значением объекта для использования в хеш-структурах данных, например в HashMap и HashSet.

Контракт метода определяет несколько ключевых пунктов:

  • Если два объекта равны согласно equals(Object), то их hashCode() должны быть равны.
  • В течение выполнения одного процесса виртуальной машины значение hashCode() для одного и того же объекта должно оставаться неизменным, пока состояние объекта, участвующее в сравнении equals, не изменится.
  • Если два объекта не равны, то одинаковые хеш-значения возможны (коллизии), но эффективная реализация должна минимизировать их число.

Стандартная реализация в java.lang.Object возвращает значение, основанное на внутреннем представлении объекта (обычно адрес или значение, связанное с адресом), но спецификация не гарантирует стабильность между запусками JVM. Для получения хеша, независимого от реализации JVM, применяются переопределения в классах-значениях (например, String, обёртки примитивов, записи).

Особенности и варианты возвращаемых значений:

  • Возвращаемое значение может быть отрицательным или положительным, диапазон полного int.
  • Для массивов рекомендуется использовать Arrays.hashCode или Arrays.deepHashCode для вложенных структур.
  • Для безопасной обработки null удобен Objects.hashCode(obj), который возвращает 0 для null и вызывает obj.hashCode() для ненулевых ссылок.

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

  • System.identityHashCode(obj) - возвращает хеш по идентичности объекта независимо от переопределённого hashCode().
  • Objects.hash(Object... values) - формирует комбинированный хеш из нескольких полей.

Когда используется

Основное применение - ключи в хеш-таблицах и структуры, зависящие от быстрого распределения объектов по корзинам. Также используется для оптимизаций и кэширования.

Краткие примеры использования

1) Значение по умолчанию из Object

public class DemoDefault {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        System.out.println(o1.hashCode());
        System.out.println(o2.hashCode());
    }
}
Пример вывода (значения зависят от JVM):
366712642
1829164700

2) Переопределение hashCode вместе с equals

public class Person {
    private final int id;
    private final String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person p = (Person) o;
        return id == p.id;
    }

    @Override
    public int hashCode() {
        return Integer.hashCode(id);
    }

    public static void main(String[] args) {
        Person a = new Person(1, "A");
        Person b = new Person(1, "B");
        System.out.println(a.equals(b));
        System.out.println(a.hashCode() + " " + b.hashCode());
    }
}
true
1 1

3) Использование Objects.hash и Arrays.hashCode

import java.util.Objects;
import java.util.Arrays;

public class HashHelpers {
    public static void main(String[] args) {
        int h1 = Objects.hash("a", 123);
        int h2 = Arrays.hashCode(new int[]{1,2,3});
        int hid = System.identityHashCode(new String("x"));
        System.out.println(h1);
        System.out.println(h2);
        System.out.println(hid);
    }
}
Пример вывода:
-1473186490
30817
2018699554

Похожие средства в Java

  • System.identityHashCode(Object) - возвращает хеш по идентичности объекта, игнорируя переопределённый hashCode(). Удобен для отладки или когда требуется уникальность по ссылке.
  • Objects.hash(Object...) - формирует комбинированный хеш из набора полей; прост в применении, но менее эффективен по сравнению с ручной реализацией для производительных структур.
  • Arrays.hashCode/Arrays.deepHashCode - для массивов одноуровневые и рекурсивные хеши соответственно. Предпочтительнее при работе с массивами вместо ручной агрегации элементов.
  • Integer.hashCode/Long.hashCode и подобные утилиты - полезны при комбинировании хешей примитивных полей.

Выбор зависит от задачи: для простоты и читабельности - Objects.hash, для производительности и контроля - ручное комбинирование с простым множителем (31) или использование специализированных библиотек.

Аналоги и особенности в других языках

PHP

// spl_object_hash - уникальная строка для объекта в рамках запроса
$o = new stdClass();
echo spl_object_hash($o) . PHP_EOL;
// начиная с PHP 7.2 есть spl_object_id
echo spl_object_id($o) . PHP_EOL;
Пример вывода:
0000000045f3c7b8000000007b1b1d9a
79

JavaScript

Встроенной функции hashCode для объектов нет. Для идентичности используется Map как ключ-объект. Для вычисления числового хеша строки применяются пользовательские функции.

// Пример простого хеширования строки
function strHash(s){
  let h=0; for(let i=0;i
Пример вывода:
-964404313

Python

# В Python встроен hash() и метод __hash__
class P:
    def __init__(self,x): self.x=x
    def __hash__(self):
        return hash(self.x)

p=P(10)
print(hash(p))
Пример вывода:
10

Объекты, для которых не определён __hash__ или которые изменяемы, могут быть не пригодны в качестве ключей.

SQL

-- SQL Server: HASHBYTES
SELECT HASHBYTES('SHA2_256', CONVERT(varbinary(MAX), 'text'));
Возвращает бинарный хеш, отличающийся от Java int и пригодный для криптографической проверки.

C#

// Object.GetHashCode() - аналог Java
class P { public int Id; public override int GetHashCode() => Id.GetHashCode(); }
Поведение и контракт близки к Java: при переопределении Equals требуется переопределять GetHashCode.

Lua

-- Таблицы в Lua хешируются по ссылке; tostring возвращает представление с адресом
local t = {}
print(tostring(t))
Пример вывода:
table: 0x7f9d9400a0

Go

// Нет универсального hashCode; для строк есть hashing по алгоритму в пакете hash/fnv
package main
import (
  "fmt"; "hash/fnv"
)
func main(){
  h:=fnv.New32a(); h.Write([]byte("abc")); fmt.Println(h.Sum32())
}
Пример вывода:
1335831723

Kotlin

// hashCode() у Kotlin аналогичен Java; data class генерирует equals и hashCode автоматически
data class D(val x:Int)
fun main(){ println(D(1).hashCode()) }
Пример вывода:
1

Отличие от Java: в некоторых языках хеш-функции направлены на криптографические задачи или возвращают бинарные строки; в JVM-подобных языках (Kotlin, Scala) контракт близок к Java.

Типичные ошибки и их проявления

1) Изменяемые поля в hashCode

Если объект используется как ключ в HashSet/HashMap, а поля, участвующие в вычислении хеша, изменяются после вставки, поиск и удаление могут перестать работать.

import java.util.HashSet;

class Item {
    int id;
    public Item(int id){this.id=id;}
    @Override public int hashCode(){ return id; }
    @Override public boolean equals(Object o){
        return (o instanceof Item) && ((Item)o).id==id;
    }
    public static void main(String[] args){
        HashSet s = new HashSet<>();
        Item it = new Item(1);
        s.add(it);
        System.out.println(s.contains(it));
        it.id = 2; // изменился хеш
        System.out.println(s.contains(it));
        s.remove(it);
        System.out.println(s.size());
    }
}
Вывод:
true
false
1

Объяснение: объект больше не находится в той же корзине, поэтому remove не удаляет его.

2) Переопределён equals, но не hashCode

import java.util.HashSet;
class P{
    int id; P(int id){this.id=id;}
    @Override public boolean equals(Object o){ return (o instanceof P) && ((P)o).id==id; }
}
public class T{ public static void main(String[] args){
    HashSet

hs = new HashSet<>(); hs.add(new P(1)); hs.add(new P(1)); System.out.println(hs.size()); }}

Вывод:
2

Объяснение: equals указывает равенство, но разные hashCode (наследуются от Object), поэтому HashSet допускает дубликаты.

3) Ожидание стабильности между запусками JVM

Значение Object.hashCode() по умолчанию не гарантирует одинаковость между процессами. Для распределённых систем нужно использовать детерминированные хеши (MD5, SHA, Murmur3) вместо hashCode().

Изменения и эволюция

  • Сигнатура Object.hashCode() остаётся неизменной на протяжении многих версий Java.
  • Появление вспомогательных утилит: Objects.hash() (Java 7) и улучшения в Arrays для хеширования массивов доступны давно и являются стандартной практикой.
  • Записи (records) получили автоматически сгенерированные equals и hashCode начиная с Java 16, что упрощает корректную реализацию для классов-значений.
  • В JVM могли происходить внутренние оптимизации реализации Object.hashCode(), но специфика контракта остаётся прежней.

Расширенные и нечастые приёмы

1) Классическая схема комбинирования полей (множитель 31)

Пример java
public class ComplexKey {
    int a; long b; String c;
    @Override public int hashCode(){
        int result = Integer.hashCode(a);
        result = 31 * result + Long.hashCode(b);
        result = 31 * result + (c == null ? 0 : c.hashCode());
        return result;
    }
}
Комментарий: 31 выбран за хорошие распределительные свойства и быстрые операции (умножение и суммирование).

2) Использование System.identityHashCode для отладки

Пример java
public class IdentityDemo {
    public static void main(String[] args){
        String s1 = new String("x");
        String s2 = new String("x");
        System.out.println(s1.hashCode()); // значение от содержимого
        System.out.println(s2.hashCode());
        System.out.println(System.identityHashCode(s1));
        System.out.println(System.identityHashCode(s2));
    }
}
Вывод (пример):
120
120
2018699554
1311053135

Комментарий: identityHashCode показывает различие по адресу независимо от equals/hashCode строки.

3) Стратегии для распределённых систем

Пример java
// Для устойчивого распределения используется крипто- или не крипто-хеш
import java.security.MessageDigest;
import java.util.Base64;

public class StableHash {
    public static String sha256(String s) throws Exception{
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] d = md.digest(s.getBytes("UTF-8"));
        return Base64.getEncoder().encodeToString(d);
    }
    public static void main(String[] args) throws Exception{
        System.out.println(sha256("key1"));
    }
}
Пример вывода:
XUFAKrxLKna5cZ2REBfFkg==... (длинная base64-строка)

Комментарий: такой хеш детерминирован и одинаков между запусками и платформами, но не совместим с Java hashCode.

4) Использование сторонних библиотек (MurmurHash, Guava)

Пример java
// Пример идеи: Guava Hashing.murmur3_32().hashUnencodedChars("text").asInt();
// даёт стабильный 32-битный хеш с хорошим распределением
Комментарий: полезно для распределённых кэшей и шардирования, лучше чем простые комбинации полей.

5) Хеширование больших структур и потоковых данных

Пример java
// Для больших объектов рекомендуется использовать потоковое вычисление хеша (MessageDigest)
// вместо вычисления большого временного объекта в памяти.
Комментарий: экономия памяти и согласованность результатов важны для больших данных.

6) Хеширование для mutable keys - копия immutable snapshot

Пример java
// Если ключи изменяемы, хранится их иммутабельная копия состояния для ключа
// или хеш вычисляется по snapshot-полям при вставке в карту.
Комментарий: это предотвращает проблемы с поиском и удалением при изменении полей ключа.

джава Object.hashCode() function comments

En
Object.hashCode() Возвращает хэш-код объекта