Java equals() en hashCode()

Java equals() en hashCode() methoden zijn aanwezig in de Object-klasse. Dus elke Java-klasse krijgt de standaardimplementatie van equals() en hashCode(). In deze post zullen we in detail kijken naar de equals() en hashCode() methoden in Java.

Java equals()

De equals() methode is gedefinieerd in de Object-klasse als volgt:

public boolean equals(Object obj) {
        return (this == obj);
}

Volgens de Java-documentatie van de equals() methode moet elke implementatie zich houden aan de volgende principes.

  • Voor elk object x zou x.equals(x) true moeten retourneren.
  • Voor elk twee objecten x en y zou x.equals(y) true moeten retourneren als en alleen als y.equals(x) true retourneert.
  • Voor meerdere objecten x, y en z, als x.equals(y) true retourneert en y.equals(z) true retourneert, dan zou x.equals(z) true moeten retourneren.
  • Meerdere oproepen van x.equals(y) zouden hetzelfde resultaat moeten retourneren, tenzij een van de eigenschappen van het object is gewijzigd die wordt gebruikt in de implementatie van equals().
  • De implementatie van de equals() methode in de Object-klasse retourneert alleen true wanneer beide verwijzingen naar hetzelfde object wijzen.

Java hashCode()

Java Object hashCode() is een native methode en geeft de gehele hashcode-waarde van het object terug. De algemene overeenkomst van de hashCode() methode is:

  • Meerdere oproepen van de hashCode() methode moeten dezelfde gehele waarde retourneren, tenzij de eigenschap van het object wordt gewijzigd die wordt gebruikt in de equals() methode.
  • De hashcode-waarde van een object kan veranderen bij meerdere uitvoeringen van dezelfde toepassing.
  • Als twee objecten gelijk zijn volgens de equals() methode, moet hun hashcode hetzelfde zijn.
  • Als twee objecten ongelijk zijn volgens de equals() methode, moeten hun hashcodes niet noodzakelijk verschillend zijn. Hun hashcodewaarde kan al dan niet gelijk zijn.

Belang van equals() en hashCode() methode

Java hashCode() en equals() methode worden gebruikt in op hash-tabel gebaseerde implementaties in Java voor het opslaan en ophalen van gegevens. Ik heb dit gedetailleerd uitgelegd op Hoe HashMap werkt in Java? De implementatie van equals() en hashCode() moet deze regels volgen.

  • Als o1.equals(o2), dan moet o1.hashCode() == o2.hashCode() altijd true zijn.
  • Als o1.hashCode() == o2.hashCode waar is, betekent dit niet noodzakelijk dat o1.equals(o2) waar zal zijn.

Wanneer moet je de equals() en hashCode() methoden overschrijven?

Wanneer we de equals()-methode overschrijven, is het bijna noodzakelijk om ook de hashCode()-methode te overschrijven, zodat hun contract niet wordt geschonden door onze implementatie. Merk op dat je programma geen uitzonderingen zal werpen als het contract van equals() en hashCode() wordt geschonden, als je niet van plan bent om de klasse als sleutel in een hashtabel te gebruiken, dan zal dit geen probleem veroorzaken. Als je van plan bent om een klasse als sleutel in een hashtabel te gebruiken, dan is het een must om zowel equals() als hashCode() methoden te overschrijven. Laten we zien wat er gebeurt als we vertrouwen op de standaardimplementatie van equals() en hashCode() methoden en een aangepaste klasse als HashMap-sleutel gebruiken.

package com.journaldev.java;

public class DataKey {

	private String name;
	private int id;

        // getter- en setter-methoden

	@Override
	public String toString() {
		return "DataKey [name=" + name + ", id=" + id + "]";
	}

}
package com.journaldev.java;

import java.util.HashMap;
import java.util.Map;

public class HashingTest {

	public static void main(String[] args) {
		Map<DataKey, Integer> hm = getAllData();

		DataKey dk = new DataKey();
		dk.setId(1);
		dk.setName("Pankaj");
		System.out.println(dk.hashCode());

		Integer value = hm.get(dk);

		System.out.println(value);

	}

	private static Map<DataKey, Integer> getAllData() {
		Map<DataKey, Integer> hm = new HashMap<>();

		DataKey dk = new DataKey();
		dk.setId(1);
		dk.setName("Pankaj");
		System.out.println(dk.hashCode());

		hm.put(dk, 10);

		return hm;
	}

}

Wanneer we bovenstaand programma uitvoeren, zal het null afdrukken. Dit komt omdat de hashCode()-methode van Object wordt gebruikt om de emmer te vinden om naar de sleutel te zoeken. Aangezien we geen toegang hebben tot de sleutels van de HashMap en we de sleutel opnieuw aanmaken om de gegevens op te halen, zul je merken dat de hashcode-waarden van beide objecten verschillend zijn en dus de waarde niet gevonden wordt.

Implementatie van equals() en hashCode() methode

We kunnen onze eigen equals() en hashCode() methoden definiëren, maar als we ze niet zorgvuldig implementeren, kan dit vreemde problemen veroorzaken tijdens runtime. Gelukkig bieden de meeste moderne IDE’s tegenwoordig manieren om ze automatisch te genereren, en indien nodig kunnen we ze aanpassen aan onze eisen. We kunnen Eclipse gebruiken om automatisch equals() en hashCode() methoden te genereren. Hier zijn de automatisch gegenereerde implementaties van de equals() en hashCode() methoden.

@Override
public int hashCode() {
	final int prime = 31;
	int result = 1;
	result = prime * result + id;
	result = prime * result + ((name == null) ? 0 : name.hashCode());
	return result;
}

@Override
public boolean equals(Object obj) {
	if (this == obj)
		return true;
	if (obj == null)
		return false;
	if (getClass() != obj.getClass())
		return false;
	DataKey other = (DataKey) obj;
	if (id != other.id)
		return false;
	if (name == null) {
		if (other.name != null)
			return false;
	} else if (!name.equals(other.name))
		return false;
	return true;
}

Let op dat zowel de equals() als hashCode() methoden dezelfde velden gebruiken voor de berekeningen, zodat hun contract geldig blijft. Als je het testprogramma opnieuw uitvoert, krijgen we het object uit de map en zal het programma 10 afdrukken. We kunnen ook Project Lombok gebruiken om equals en hashCode methoden automatisch te genereren.

Wat is een hash-collision

In zeer eenvoudige bewoordingen gebruiken Java-hash-tabelimplementaties de volgende logica voor get- en put-operaties.

  1. Identificeer eerst de “Bucket” die moet worden gebruikt met behulp van de hashcode van de “key”.
  2. Als er geen objecten aanwezig zijn in de emmer met dezelfde hashcode, voeg dan het object toe voor de put-operatie en geef null terug voor de get-operatie.
  3. Als er andere objecten in de emmer zijn met dezelfde hashcode, komt de “sleutel” equals-methode in het spel.
    • Als equals () true retourneert en het een put-operatie is, wordt de objectwaarde overschreven.
    • Als equals () false retourneert en het een put-operatie is, wordt er een nieuwe vermelding aan de emmer toegevoegd.
    • Als equals () true retourneert en het een get-operatie is, wordt de objectwaarde geretourneerd.
    • Als equals () false retourneert en het een get-operatie is, wordt null geretourneerd.

Onderstaande afbeelding toont een emmer met items van HashMap en hoe hun equals() en hashCode() gerelateerd zijn. Het verschijnsel waarbij twee sleutels dezelfde hashcode hebben, wordt hashbotsing genoemd. Als de hashCode ()-methode niet correct is geïmplementeerd, zullen er meer hashbotsingen optreden en zullen de vermeldingen in de map niet goed worden verdeeld, wat traagheid veroorzaakt bij de get- en put-operaties. Dit is de reden voor het gebruik van priemgetallen bij het genereren van hashcodes, zodat de vermeldingen in de map goed worden verdeeld over alle emmers.

Wat als we zowel hashCode() als equals() niet implementeren?

We hebben hierboven al gezien dat als hashCode() niet geïmplementeerd is, we de waarde niet kunnen ophalen omdat HashMap de hashcode gebruikt om de emmer te vinden waarin naar de invoer wordt gezocht. Als we alleen hashCode() gebruiken en equals() niet implementeren, wordt de waarde ook niet opgehaald omdat de equals() -methode false zal retourneren.

Best Practices voor het implementeren van de equals() en hashCode() methode

  • Gebruik dezelfde eigenschappen in zowel de equals() als hashCode() implementaties, zodat hun contract niet wordt geschonden wanneer een eigenschap wordt bijgewerkt.
  • Het is beter om onveranderlijke objecten te gebruiken als sleutel voor de hash-tabel, zodat we de hashcode kunnen cachen in plaats van deze bij elke oproep te berekenen. Daarom is String een goede kandidaat voor de sleutel van de hash-tabel omdat het onveranderlijk is en de hashcode-waarde kan cachen.
  • Implementeer de hashCode() methode zodat het aantal hash-botsingen minimaal is en de invoer gelijkmatig over alle emmers wordt verdeeld.

Je kunt de volledige code downloaden van onze GitHub Repository.

Source:
https://www.digitalocean.com/community/tutorials/java-equals-hashcode