Artykuł będzie związany z kolekcją "HashSet" (zbiór), a konkretniej z programowaniem kiedy ma uznać dwa obiekty za "równe". "hashCode" w języku Java (zwany "kodem skrótu") jest pierwszym składnikiem tego tematu (drugim jest metoda "equals"). Jesteś zaciekawiony(-a) tematu definiowania identyczności na podstawie jakich danych składowych? Nie pozostaje Ci nic innego jak wchodzić do środka artykułu i przeczytać 📖!

CZYM JEST "HASHCODE" W JĘZYKU JAVA?

Hasło w nagłówku nazywane jest również "kodem skrótu". Jest to nietypowy "zlepek" kilku osobnych informacji, z których powstaje liczba całkowita. Gdy utworzymy sobie dowolny obiekt np. łańcuch znaków i wypiszemy na ekranie terminala wywołanie metody "hashCode":

var string = "Mój łańcuch znaków";

System.out.println(string.hashCode());

 ujrzymy coś takiego:

-1081490814

Nie pokazuję rezultatu dla samych numerków, tylko żeby wprowadzić Cię do teorii identyczności obiektów. Pierwszą częścią jest zapewnienie, żeby algorytm wyznaczający tę wartość nie spowodował zwrócenie tej samej wartości przy dwóch RÓŻNYCH kombinacjach. Na przykładzie łańcucha znaków, nie może dojść do sytuacji, w której dwa różne łańcuchy zwrócą identyczną wartość liczbową, bo to będzie oznaczało złe dobranie algorytmu. "var", swoją drogą, to słowo kluczowe, które pozwala uniknąć każdorazowego wpisywania samemu typu danych - po więcej informacji odsyłam do osobnego artykułu.

Jeżeli zbiór ma obsługiwać niestandardowy typ danych np. zdefiniowaną przez nas klasę, to nieuniknione jest przesłonięcie metody "hashCode" w języku Java i opracowanie niezawodnego systemu przeliczania wartości typu "int". "hashCode" należy do klasy "Object" będącej nazywaną "matką wszystkich obiektów", gdyż od niej pochodzi każda inna klasa i każdy obiekt może się powołać na tę metodę w każdej chwili (dotyczy to też naszej klasy bez przesłaniania):

var myClassInstance = new MyClass();

System.out.println(myClassInstance.hashCode());

Oczywiście samo przesłanianie nie jest obowiązkowe, tylko wtedy nasza weryfikacja identyczności obiektów jest zawodna ⚠️.

Jeżeli nasz "HashSet" (albo dowolna inna kolekcja należąca do typu "zbiór") ma rzeczywiście niezawodnie identyfikować identyczność wprowadzanych elementów, musimy przesłonić DWIE metody. Oprócz tej tytułowej, dochodzi jeszcze o nazwie "equals" opisanej szerzej w osobnym artykule.

Oto domyślna struktura metody "hashCode" 👇:

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

Jak wspomniałem wcześniej - wartość kodu skrótu jest liczbą całkowitą. Zakładam, że wiesz jak "super" wpływa na wywoływanie metod (jak nie, to śmiało wchodź do załącznika 😉) 🙂.

PRZYKŁAD KODU ŹRÓDŁOWEGO

Pokażę Ci teraz przypadek co zrobić, jak chcemy zaimplementować proste przesłonięcie metody "hashCode" w języku Java na przykładzie własnej klasy. Załóżmy, że mamy w niej łańcuch znaków:

private String string;

i chcielibyśmy właśnie na nim opierać późniejsze porównywanie ze sobą obiektów. Akurat w sytuacji korzystania z łańcuchów jest najprostsza sprawa bowiem możemy użyć już przygotowanej implementacji, wywołując po prostu "hashCode":

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

A jak będziemy chcieli dwa? Postępujemy analogicznie. Nowa składowa u góry:

private String stringB;

a w przesłonięciu, suma wartości kodów skrótu:

@Override
public int hashCode() {
	return string.hashCode() + stringB.hashCode();
}

ŻYCIE BEZ ŁAŃCUCHÓW

Pozostaje jeszcze jedna rzecz do wyjaśnienia. Co zrobić, gdy nie możemy (lub nie chcemy) polegać na łańcuchach znaków i jesteśmy skazani na przykład na liczby całkowite? Weźmy sobie za przykład punkt w układzie współrzędnych z taką implementacją:

public record Point(int x, int y) {
	
}

Zamiast klasy, wstawiłem rekord, bo nie zależy nam na edytowaniu później tych wartości. Nie ma łańcuchów i musimy skonstruować przeliczanie operując tylko na współrzędnych (x; y). Dobrym podejściem jest pójście w ślady implementacji kodu skrótu dla łańcucha, który składa się z takiej oto postaci:

string[0]*31(n - 1) + string[1]*31(n - 2) + ... + string[n - 1]

Łańcuch znaków "podskórnie" jest tablicą składającą się z pojedynczych znaków, więc "string[0]" oznacza pierwszy element tablicy itd. Następnie, po przekonwertowaniu znaku na liczbę całkowitą (bo "char" jest łatwo zamieniany na "int"), wartość jest przemnożona przez 31 i podniesiona do odpowiedniej potęgi według podejścia podczas wyznaczania wartości z systemu binarnego.

Formuła nazywa się "rolling hash" i jest uznawana za bardzo dobry sposób na wyznaczanie kodu skrótu, zarówno pod kątem niezawodności, jak i efektywności działania 💯. Po więcej informacji, odsyłam już do sieci.

Mając nasze dwie współrzędne, możemy skorzystać z tego wzoru i bezpośrednio podstawić:

public record Point(int x, int y) {
	@Override
	public int hashCode() {
		return x*31 + y;
	}
}

To w zupełności wystarczy, aby nie doszło do sytuacji, w której dwa punkty o różnych współrzędnych otrzymały ten sam kod skrótu.

Metoda "hashCode" w języku Java

Metoda "hashCode" w języku Java zwraca liczbę całkowitą kodu skrótu danego obiektu. Dla nowo tworzonych klas, należy ją przesłonić i zdefiniować odpowiedni wzór na przeliczanie kodu skrótu dla zapewnienia prawidłowej identyfikacji identycznych obiektów.


Tyle! Mam nadzieję, że wyniesiesz z tego taką lekcję, że określając sposób przeliczania kodu skrótu, musisz tak dobrać wzór, żeby przydzielał niezawodnie unikalną kombinację zależną od wartości wyznaczonych składowychTo konkluduje pierwszą część nt. definiowania identyczności obiektów. Druga to metoda "equals", więc możesz przejść do niej już teraz klikając poniżej 👇.

NASTĘPNY ARTYKUŁ: equals w języku Java. Definiowanie identyczności obiektów

PODOBNE ARTYKUŁY