Jason. Cała informatyka w jednym miejscu!

Przechodzimy do części praktycznej "równości" obiektów w Javie. Trzymamy się kolekcji "HashSet" i rzucam następującymi hasłami: "hashCode" w języku Java (zwany "kodem mieszającym") i "equals". Jesteście zaciekawieni znaczeń tych metod i w jaki sposób mogą pomóc nam przy ustalaniu czy dwa obiekty są sobie równe? Nie pozostaje Wam nic innego jak wchodzić do środka artykułu i czytać!

CZYM JEST "HASHCODE" W JĘZYKU JAVA?

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

public class Main {
	public static void main(String[] args) {
		String s = "Nowy łańcuch znaków";

		System.out.println(s.hashCode());
	}
}

ujrzymy coś takiego:

Kod mieszający zwrócony przez metodę "hashCode" w języku Java

Kod zwracany przez metodę "hashCode" w języku Java zależy od wielu czynników których nie sposób wymienić i na nim skupimy swoją uwagę w niniejszym artykule. Poprzednia część wprowadzała kolekcję "HashSet" wraz z teoretycznym wprowadzeniem do samodzielnego definiowania kiedy dwa obiekty możemy uznać za "równe", aby zbiór nie przechowywał dwóch takich samych elementów, bo wtedy jego rola staje się nic niewarta. Możemy to sobie zamienić na "ArrayList" i dalej efekt będzie taki sam, ale nie to jest naszym celem.

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". Obie metody należą do klasy "Object" będącej nazywaną "matką wszystkich obiektów", gdyż od niej pochodzi każda inna klasa. Zatem, pierwszym krokiem na udane weryfikowanie "równości" obiektów jest jawne wprowadzenie obu metod do klasy, która ma przechowywać kryterium identyczności. Oto domyślna struktura:

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

@Override
public boolean equals(Object obj) {
	return super.equals(obj);
}

Mamy nasze metody "hashCode" oraz "equals". Zauważcie, że jedna zwraca liczbę całkowitą, a druga "boolean'a". Zakładam, że wiecie jak "super" wpływa na wywoływanie metod.

PRZYKŁAD KODU ŹRÓDŁOWEGO

Czas wreszcie zaprezentować w jaki sposób możemy wpływać na sprawdzenie czy jeden obiekt jest taki sam, jak drugi. Odstąpię wyjątkowo od wklejania tego samego kodu. Całość opiera się o ten sam kod zaprezentowany tutaj więc mogę jedynie skupić się metodach wprowadzanych do klasy "Book".

Zacznijmy od tytułu. Załóżmy, że tytuł jest kluczowy którego wartość jest identyczna. Czyli "życiowo" rzecz ujmując, nie trafia się nigdy książka nosząca ten sam tytuł. Zatem, "hashCode" w języku Java dla klasy "Book" może wyglądać tak:

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

a metoda "equals" wtedy ma wyglądać tak:

@Override
public boolean equals(Object object) {
	if(getClass() != object.getClass()) {
		return false;
	}
	
	Book book = (Book)object;

	return title.equals(book.title);
}

Jeśli łańcuch znaków ma być unikatowy, to możemy odwołać się do kodu mieszającego pobieranego z łańcucha. Natomiast jeśli uzależniamy weryfikację unikatowości od łańcucha znaków, to MUSIMY również zająć się "equals" i tam operacja wygląda ciut bardziej przerażająca. Rzutujemy obiekt na naszą klasę i wtedy możemy korzystać ze wszystkich jego składowych. Radzę też dorzucić instrukcję warunkową dla sprawdzenia klas obu obiektów, aby od razu zwrócić wartość fałszywą, jeżeli są to różne klasy. Jeżeli teraz uruchomicie ten sam program to GWARANTUJĘ, że próba wstawienia książki o tym samym tytule nie będzie wnosiła żadnego efektu. Nie będzie żadnego błędu ani wyjątku, tylko po prostu wywołanie metody będzie "wytłumione", tak samo jakby się wywołało pustą metodę.

Co w przypadku gdybyśmy chcieli uwzględnić nie tylko sam tytuł, ale również autora? Robimy podobnie z tym, że teraz "hashCode" w języku Java przyjmuje sumę kodów mieszających:

@Override
public int hashCode() {
	return title.hashCode() + author.hashCode();
}

a "equals" koniunkcję warunków:

@Override
public boolean equals(Object object) {
	if(getClass() != object.getClass()) {
		return false;
	}	
	
	Book book = (Book)object;
	
	return title.equals(book.title) && author.equals(book.author);
}

Powinniście już kapować jak to działa. Dzięki takiemu zabiegowi możemy ustalać dowolne kryterium na którym będzie się opierać zbiór celem zweryfikowania czy dodawany element należy "wpuścić" do siebie, czy nie.

Mógł nasunąć się jeden "zong". Skąd łańcuch znaków "wie" jaki ma zwrócić kod mieszający? Na tym przykładzie korzystamy ze standardowej implementacji nie wnikając w algorytm kalkulowania kodu mieszającego, aczkolwiek zawsze można przesłonić tę metodę i wziąć tę odpowiedzialność na własne barki. Kiedy zdecydujecie się na taki manewr (albo nie będziecie mieli innego wyjścia, bo Wasza klasa będzie potrzebowała takich obliczeń), pamiętajcie że to nie może być byle jaka wartość. Algorytm musi Wam dobierać taki kod, żeby dwie różne kombinacje nie zwróciły tego samego wyniku! Pisząc o kombinacjach, mam na myśli dla przykładu punkty w układzie współrzędnych. Jeśli mamy dwie pary RÓŻNYCH liczb "x" i "y" i w obu obiektach wyszedł ten sam kod mieszający, to znak że Wasz algorytm jest do niczego!!!


Tyle! Mam nadzieję, że wyniesiecie z tego jakąś lekcję i od tej chwili będziecie wiedzieli jak można zlikwidować problem przechowywania duplikatów przez kolekcję, u której w ogóle taka sytuacja nie powinna mieć miejsca.

PODOBNE ARTYKUŁY