Das Hash-Prinzip

Bevor wir den Java-Hashcode definieren, müssen wir erst einmal verstehen, was Hashing ist und wofür es eingesetzt wird. Beim Hashing wird eine Hash-Funktion auf bestimmte Daten angewendet. Eine Hash-Funktion ist einfach eine mathematische Funktion. Mach dir darüber keine Gedanken! „Mathematisch“ bedeutet nicht immer „kompliziert“. Hier bedeutet es nur, dass wir Daten und eine bestimmte Regel haben, die die Daten auf eine Zeichenmenge (Code) abbildet. Das könnte zum Beispiel eine hexadezimale Chiffre sein. Wir haben Daten beliebiger Größe als Eingabe und wenden auf sie eine Hash-Funktion an. Als Ausgabe erhalten wir Daten mit einer festen Größe, zum Beispiel 32 Zeichen. Normalerweise wandelt eine solche Funktion eine große Datenmenge in einen kleinen ganzzahligen Wert um. Das Ergebnis dieser Funktion wird als Hashcode bezeichnet. Hash-Funktionen werden häufig in der Kryptografie und in einigen anderen Bereichen eingesetzt. Hash-Funktionen können unterschiedlich sein, aber sie haben alle bestimmte Eigenschaften:
  • Ein bestimmtes Objekt hat einen bestimmten Hashcode.
  • Wenn zwei Objekte gleich sind, sind ihre Hashcodes identisch. Das Gegenteil ist nicht der Fall.
  • Wenn die Hashcodes unterschiedlich sind, dann sind die Objekte mit Sicherheit nicht gleich.
  • Verschiedene Objekte können den gleichen Hashcode haben. Allerdings ist das ein sehr unwahrscheinliches Ereignis. An diesem Punkt haben wir eine Kollision, eine Situation, in der wir Daten verlieren können.
Die „richtige“ Hash-Funktion minimiert die Wahrscheinlichkeit von Kollisionen.

Hashcode in Java

In Java ist die Hash-Funktion normalerweise mit der Methode hashCode() verbunden. Genau genommen ist das Ergebnis der Anwendung einer Hash-Funktion auf ein Objekt ein Hashcode. Jedes Java-Objekt hat einen Hashcode. Im Allgemeinen ist ein Hashcode eine Zahl, die mit der Methode hashCode() der Klasse Object berechnet wird. In der Regel überschreiben Programmierer diese Methode für ihre Objekte, ebenso wie die mit hashCode() verwandte equals()-Methode, um bestimmte Daten effizienter verarbeiten zu können. Die Methode hashCode() gibt einen int-Wert (4 Byte) zurück, der eine numerische Darstellung des Objekts ist. Dieser Hashcode wird zum Beispiel von Collections verwendet, um Daten effizienter zu speichern und dementsprechend schneller auf sie zuzugreifen. Standardmäßig gibt die Funktion hashCode() für ein Objekt die Nummer der Speicherzelle zurück, in der das Objekt gespeichert ist. Wenn also keine Änderungen am Anwendungscode vorgenommen werden, sollte die Funktion denselben Wert zurückgeben. Wenn sich der Code leicht ändert, ändert sich auch der Hashcode-Wert. Wofür wird der Hashcode in Java verwendet? Zunächst einmal helfen Java-Hashcodes dabei, dass Programme schneller laufen. Wenn wir zum Beispiel zwei Objekte o1 und o2 eines bestimmten Typs vergleichen, braucht die Operation o1.equals(o2) etwa 20 Mal mehr Zeit als o1.hashCode() == o2.hashCode().

Java equals()

In der übergeordneten Klasse Object gibt es neben der Methode hashCode() auch die Funktion equals(), mit der die Gleichheit von zwei Objekten geprüft wird. Die Standardimplementierung dieser Funktion prüft einfach die Verknüpfungen zweier Objekte auf ihre Gleichwertigkeit. equals() und hashCode() haben ihren eigenen Vertrag. Wenn du also eine der beiden Funktionen überschreibst, solltest du auch die andere überschreiben, um diesen Vertrag nicht zu brechen.

Implementierung der Methode hashCode()

Beispiel

Lass uns eine Klasse Character mit einem Feld erstellen: Name. Danach erstellen wir zwei Objekte der Klasse Character, character1 und character2, und geben ihnen denselben Namen. Wenn wir die Standardmethoden hashCode() und equals() der Klasse Object verwenden, erhalten wir definitiv unterschiedliche, nicht gleiche Objekte. So funktioniert Hashcode in Java. Sie haben unterschiedliche Hashcodes, weil sie sich in unterschiedlichen Speicherzellen befinden, und das Ergebnis der Operation equals() wird „false“ sein.

import java.util.Objects;

public class Character {
    private String Name;

    public Character(String name) {
        Name = name;
    }

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    } 

    public static void main(String[] args) {
        Character character1 = new Character("Arnold");
        System.out.println(character1.getName());
        System.out.println(character1.hashCode());
        Character character2 = new Character("Arnold");
        System.out.println(character2.getName());
        System.out.println(character2.hashCode());
        System.out.println(character2.equals(character1));
    }
}
Das Ergebnis der Ausführung des Programms:

Arnold
1595428806
Arnold
1072408673
false
Zwei 10-stellige Zahlen in der Konsole sind die Hashcodes. Was ist, wenn wir gleiche Objekte haben wollen, wenn sie die gleichen Namen haben? Was sollen wir tun? Die Antwort: Wir sollten die Methoden hashCode() und equals() der Klasse Object für unsere Klasse Character überschreiben. Wir können das in der IDEA IDE automatisch machen. Drücke einfach alt + einfügen auf deiner Tastatur und wähle Generieren -> equals() und hashCode(). Was ist Java hashCode() – 2Im Fall unseres Beispiels haben wir den folgenden Code:

import java.util.Objects;

public class Character {
    private String Name;

    public Character(String name) {
        Name = name;
    }

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Character)) return false;

        Character character = (Character) o;

        return getName() != null ? getName().equals(character.getName()) : character.getName() == null;
    }

    @Override
    public int hashCode() {
        return getName() != null ? getName().hashCode() : 0;
    }

    public static void main(String[] args) {
        Character character1 = new Character("Arnold");
        System.out.println(character1.getName());
        System.out.println(character1.hashCode());
        Character character2 = new Character("Arnold");
        System.out.println(character2.getName());
        System.out.println(character2.hashCode());
        System.out.println(character2.equals(character1));
    }
}
Das Ergebnis der Ausführung dieses Codes:

Arnold
1969563338
Arnold
1969563338
true
Jetzt identifiziert das Programm unsere Objekte als gleich und sie haben die gleichen Hashcodes. Was ist Hashcode in Java - 2

Java-Hashcode – Beispiel:

Deine eigenen Methoden hashCode() und equals()

Du kannst auch deine eigenen equals()- und hashCode()-Methoden erstellen, aber sei vorsichtig und denke daran, die Hashcode-Kollisionen zu minimieren. Hier ist ein Beispiel für unsere eigenen hashCode()- und equals()-Methoden in der Klasse Student:

import java.util.Date;

public class Student {
   String surname;
   String name;
   String secondName;
   Long birthday; // Long instead of long is used by Gson/Jackson json parsers and various orm databases

   public Student(String surname, String name, String secondName, Date birthday ){
       this.surname = surname;
       this.name = name;
       this.secondName = secondName;
       this.birthday = birthday == null ? 0 : birthday.getTime();
   }
//Java hashcode example
   @Override
   public int hashCode(){
       //TODO: check for nulls
       //return surname.hashCode() ^ name.hashCode() ^ secondName.hashCode() ^ (birthday.hashCode());
       return (surname + name + secondName + birthday).hashCode();
   }
   @Override
   public boolean equals(Object other_) {
       Student other = (Student)other_;
       return (surname == null || surname.equals(other.surname) )
               && (name == null || name.equals(other.name))
               && (secondName == null || secondName.equals(other.secondName))
               && (birthday == null || birthday.equals(other.birthday));
   }
}
Und die Main-Klasse, um ihre Funktionsweise zu demonstrieren:

import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;

public class Main {
   static HashMap<Student, Integer> cache = new HashMap<Student, Integer>(); // <person, targetPriority>

   public static void main(String[] args) {
       Student sarah1 = new Student("Sarah","Connor", "Jane", null);
       Student sarah2 = new Student("Sarah","Connor", "Jane", new Date(1970, 01-1, 01));
       Student sarah3 = new Student("Sarah","Connor", "Jane", new Date(1959, 02-1, 28)); // date not exists
       Student john = new Student("John","Connor", "Kyle", new Date(1985, 02-1, 28)); // date not exists
       Student johnny = new Student("John","Connor", "Kyle", new Date(1985, 02-1, 28)); // date not exists
       System.out.println(john.hashCode());
       System.out.println(johnny.hashCode());
       System.out.println(sarah1.hashCode());
       System.out.println();
       cache.put(sarah1, 1);
       cache.put(sarah2, 2);
       cache.put(sarah3, 3);
       System.out.println(new Date(sarah1.birthday));
       System.out.println();
       cache.put(john, 5);
       System.out.println(cache.get(john));
       System.out.println(cache.get(johnny));
       cache.put(johnny, 7);
       System.out.println(cache.get(john));
       System.out.println(cache.get(johnny));
   }
}

Wofür wird Hashcode verwendet?

Zunächst einmal helfen Hashcodes dabei, dass Programme schneller laufen. Wenn wir zum Beispiel zwei Objekte o1 und o2 eines bestimmten Typs vergleichen, braucht die Operation o1.equals(o2) etwa 20 Mal mehr Zeit als o1.hashCode() == o2.hashCode(). In Java steht das Hashing-Prinzip hinter einigen beliebten Collections, wie HashMap, HashSet und HashTable.

Fazit

Jedes Java-Objekt hat die Methoden hashCode() und equals() von der Klasse Object geerbt. Um einen gut funktionierenden Gleichheitsmechanismus zu erhalten, solltest du die Methoden hashcode() und equals() für deine eigenen Klassen überschreiben. Durch die Verwendung von Hashcodes laufen Programme schneller.