Wie erhalte ich die eindeutige ID eines Objekts, das hashCode () überschreibt?

231

Wenn eine Klasse in Java hashCode () nicht überschreibt , ergibt das Drucken einer Instanz dieser Klasse eine schöne eindeutige Nummer.

Das Javadoc von Object sagt über hashCode () :

Soweit dies vernünftigerweise praktikabel ist, gibt die von class Object definierte hashCode-Methode unterschiedliche Ganzzahlen für unterschiedliche Objekte zurück.

Aber wenn die Klasse hashCode () überschreibt , wie kann ich ihre eindeutige Nummer erhalten?

ivan_ivanovich_ivanoff
quelle
33
Meistens aus 'Debugging'-Gründen;) Um sagen zu können: Ah, das gleiche Objekt!
ivan_ivanovich_ivanoff
5
Zu diesem Zweck ist der System.identityHashcode () wahrscheinlich von Nutzen. Ich würde mich jedoch nicht darauf verlassen, um die Codefunktionalität zu implementieren. Wenn Sie Objekte eindeutig identifizieren möchten, können Sie AspectJ und Code-Weave in einer eindeutigen ID pro erstelltem Objekt verwenden. Mehr Arbeit aber
Brian Agnew
9
Denken Sie daran, dass hashCode NICHT garantiert eindeutig ist. Auch wenn die Implementierung die Speicheradresse als Standard-Hashcode verwendet. Warum ist es nicht einzigartig? Weil Objekte Müll sammeln und Speicher wiederverwendet wird.
Igor Krivokon
8
Wenn Sie entscheiden möchten, ob zwei Objekte gleich sind, verwenden Sie == anstelle von hashCode (). Letzteres ist selbst in der ursprünglichen Implementierung nicht garantiert eindeutig.
Mnementh
6
Keine der Antworten beantwortet die eigentliche Frage, da sie sich in der Diskussion von hashCode () verfangen, was hier zufällig war. Wenn ich mir Referenzvariablen in Eclipse ansehe, wird mir eine eindeutige unveränderliche "id = xxx" angezeigt. Wie kommen wir programmgesteuert zu diesem Wert, ohne unseren eigenen ID-Generator verwenden zu müssen? Ich möchte zu Debugging-Zwecken (Protokollierung) auf diesen Wert zugreifen, um bestimmte Instanzen von Objekten zu identifizieren. Weiß jemand, wie man diesen Wert in die Hände bekommt?
Chris Westin

Antworten:

346

System.identityHashCode (yourObject) gibt den 'ursprünglichen' Hash-Code IhresObjects als Ganzzahl an. Einzigartigkeit ist nicht unbedingt garantiert. Die Sun JVM-Implementierung gibt Ihnen einen Wert, der sich auf die ursprüngliche Speicheradresse für dieses Objekt bezieht. Dies ist jedoch ein Implementierungsdetail, auf das Sie sich nicht verlassen sollten.

BEARBEITEN: Antwort geändert nach Toms Kommentar unten bezüglich. Speicheradressen und sich bewegende Objekte.

Brian Agnew
quelle
Lassen Sie mich raten: Es ist nicht eindeutig, wenn Sie mehr als 2 ** 32 Objekte in derselben JVM haben. ;) Kannst du mich auf einen Ort hinweisen, an dem die Nicht-Einzigartigkeit beschrieben wird? Danke!
ivan_ivanovich_ivanoff
9
Es spielt keine Rolle, wie viele Objekte vorhanden sind oder wie viel Speicher vorhanden ist. Weder hashCode () noch identityHashCode () sind erforderlich, um eine eindeutige Nummer zu erzeugen.
Alan Moore
12
Brian: Es ist nicht der tatsächliche Speicherort, Sie erhalten zufällig eine aufbereitete Version einer Adresse, wenn diese zum ersten Mal berechnet wird. In einer modernen VM bewegen sich Objekte im Speicher.
Tom Hawtin - Tackline
2
Wenn also ein Objekt unter der Speicheradresse 0x2000 erstellt und dann von der VM verschoben wird, wird bei 0x2000 ein anderes Objekt erstellt. Haben sie dasselbe System.identityHashCode()?
Begrenzte Versöhnung
14
Die Eindeutigkeit ist überhaupt nicht garantiert ... für eine praktische JVM-Implementierung. Die garantierte Eindeutigkeit erfordert entweder keine Verlagerung / Verdichtung durch den GC oder eine große und teure Datenstruktur zur Verwaltung der Hashcode-Werte von lebenden Objekten.
Stephen C
28

Das Javadoc für Object gibt dies an

Dies wird normalerweise implementiert, indem die interne Adresse des Objekts in eine Ganzzahl konvertiert wird. Diese Implementierungstechnik wird jedoch von der JavaTM-Programmiersprache nicht benötigt.

Wenn eine Klasse hashCode überschreibt, bedeutet dies, dass sie eine bestimmte ID generieren möchte, die (man kann hoffen) das richtige Verhalten hat.

Sie können System.identityHashCode verwenden , um diese ID für jede Klasse abzurufen .

Valentin Rocher
quelle
6

Vielleicht funktioniert diese schnelle, schmutzige Lösung?

public class A {
    static int UNIQUE_ID = 0;
    int uid = ++UNIQUE_ID;

    public int hashCode() {
        return uid;
    }
}

Dies gibt auch die Anzahl der Instanzen einer Klasse an, die initialisiert werden.

John Pang
quelle
4
Dies setzt voraus, dass Sie Zugriff auf den Quellcode der Klasse haben
Pablisco
Wenn Sie nicht auf den Quellcode zugreifen können, erweitern Sie ihn einfach und verwenden Sie die erweiterte Klasse. Einfach schnelle, einfache und schmutzige Lösung, aber es funktioniert.
John Pang
1
es funktioniert nicht immer. Die Klasse könnte endgültig sein. Ich denke, System.identityHashCodeist eine bessere Lösung
Pablisco
2
Für die Thread-Sicherheit könnte man AtomicLongwie in dieser Antwort verwenden .
Evgeni Sergeev
Wenn die Klasse von einem anderen Klassenladeprogramm geladen wird, hat sie unterschiedliche statische UNIQUE_ID-Variablen. Bin ich richtig?
cupiqi09
6

hashCode()Die Methode dient nicht zur Bereitstellung einer eindeutigen Kennung für ein Objekt. Vielmehr wird der Status des Objekts (dh die Werte der Mitgliedsfelder) in eine einzelne Ganzzahl umgewandelt. Dieser Wert wird hauptsächlich von einigen Hash-basierten Datenstrukturen wie Karten und Sets verwendet, um Objekte effektiv zu speichern und abzurufen.

Wenn Sie eine Kennung für Ihre Objekte benötigen, empfehle ich Ihnen, eine eigene Methode hinzuzufügen, anstatt diese zu überschreiben hashCode. Zu diesem Zweck können Sie eine Basisschnittstelle (oder eine abstrakte Klasse) wie unten erstellen.

public interface IdentifiedObject<I> {
    I getId();
}

Anwendungsbeispiel:

public class User implements IdentifiedObject<Integer> {
    private Integer studentId;

    public User(Integer studentId) {
        this.studentId = studentId;
    }

    @Override
    public Integer getId() {
        return studentId;
    }
}
Ovunccetin
quelle
4

Wenn es sich um eine Klasse handelt, die Sie ändern können, können Sie eine Klassenvariable deklarieren static java.util.concurrent.atomic.AtomicInteger nextInstanceId. (Sie müssen ihm auf offensichtliche Weise einen Anfangswert geben.) Deklarieren Sie dann eine Instanzvariable int instanceId = nextInstanceId.getAndIncrement().

Aaron Mansheim
quelle
2

Ich habe diese Lösung entwickelt, die in meinem Fall funktioniert, wenn ich Objekte auf mehreren Threads erstellt habe und serialisierbar bin:

public abstract class ObjBase implements Serializable
    private static final long serialVersionUID = 1L;
    private static final AtomicLong atomicRefId = new AtomicLong();

    // transient field is not serialized
    private transient long refId;

    // default constructor will be called on base class even during deserialization
    public ObjBase() {
       refId = atomicRefId.incrementAndGet()
    }

    public long getRefId() {
        return refId;
    }
}
Howard Swope
quelle
2
// looking for that last hex?
org.joda.DateTime@57110da6

Wenn Sie sich mit den hashcodeJava-Typen befassen, während Sie .toString()ein Objekt ausführen, lautet der zugrunde liegende Code wie folgt:

Integer.toHexString(hashCode())
Frankie
quelle
0

Nur um die anderen Antworten aus einem anderen Blickwinkel zu erweitern.

Wenn Sie Hashcodes von 'oben' wiederverwenden und neue Codes unter Verwendung des unveränderlichen Status Ihrer Klasse ableiten möchten, funktioniert ein Aufruf von super. Dies kann / kann nicht bis zum Objekt kaskadieren (dh einige Vorfahren nennen Super möglicherweise nicht), ermöglicht es Ihnen jedoch, Hashcodes durch Wiederverwendung abzuleiten.

@Override
public int hashCode() {
    int ancestorHash = super.hashCode();
    // now derive new hash from ancestorHash plus immutable instance vars (id fields)
}
Glen Best
quelle
0

Es gibt einen Unterschied zwischen hashCode () und identityHashCode (). Es ist möglich, dass für zwei ungleiche (mit == getestete) Objekte o1, o2 hashCode () gleich sein kann. Sehen Sie sich das folgende Beispiel an, wie dies wahr ist.

class SeeDifferences
{
    public static void main(String[] args)
    {
        String s1 = "stackoverflow";
        String s2 = new String("stackoverflow");
        String s3 = "stackoverflow";
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
        System.out.println(s3.hashCode());
        System.out.println(System.identityHashCode(s1));
        System.out.println(System.identityHashCode(s2));
        System.out.println(System.identityHashCode(s3));
        if (s1 == s2)
        {
            System.out.println("s1 and s2 equal");
        } 
        else
        {
            System.out.println("s1 and s2 not equal");
        }
        if (s1 == s3)
        {
            System.out.println("s1 and s3 equal");
        }
        else
        {
            System.out.println("s1 and s3 not equal");
        }
    }
}
Entwickler Marius Žilėnas
quelle
0

Ich hatte das gleiche Problem und war bisher mit keiner der Antworten zufrieden, da keine von ihnen eindeutige IDs garantierte.

Ich wollte auch Objekt-IDs zum Debuggen drucken. Ich wusste, dass es eine Möglichkeit geben muss, dies zu tun, da im Eclipse-Debugger eindeutige IDs für jedes Objekt angegeben werden.

Ich habe eine Lösung gefunden, die darauf basiert, dass der Operator "==" für Objekte nur dann true zurückgibt, wenn die beiden Objekte tatsächlich dieselbe Instanz sind.

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

/**
 *  Utility for assigning a unique ID to objects and fetching objects given
 *  a specified ID
 */
public class ObjectIDBank {

    /**Singleton instance*/
    private static ObjectIDBank instance;

    /**Counting value to ensure unique incrementing IDs*/
    private long nextId = 1;

    /** Map from ObjectEntry to the objects corresponding ID*/
    private Map<ObjectEntry, Long> ids = new HashMap<ObjectEntry, Long>();

    /** Map from assigned IDs to their corresponding objects */
    private Map<Long, Object> objects = new HashMap<Long, Object>();

    /**Private constructor to ensure it is only instantiated by the singleton pattern*/
    private ObjectIDBank(){}

    /**Fetches the singleton instance of ObjectIDBank */
    public static ObjectIDBank instance() {
        if(instance == null)
            instance = new ObjectIDBank();

        return instance;
    }

    /** Fetches a unique ID for the specified object. If this method is called multiple
     * times with the same object, it is guaranteed to return the same value. It is also guaranteed
     * to never return the same value for different object instances (until we run out of IDs that can
     * be represented by a long of course)
     * @param obj The object instance for which we want to fetch an ID
     * @return Non zero unique ID or 0 if obj == null
     */
    public long getId(Object obj) {

        if(obj == null)
            return 0;

        ObjectEntry objEntry = new ObjectEntry(obj);

        if(!ids.containsKey(objEntry)) {
            ids.put(objEntry, nextId);
            objects.put(nextId++, obj);
        }

        return ids.get(objEntry);
    }

    /**
     * Fetches the object that has been assigned the specified ID, or null if no object is
     * assigned the given id
     * @param id Id of the object
     * @return The corresponding object or null
     */
    public Object getObject(long id) {
        return objects.get(id);
    }


    /**
     * Wrapper around an Object used as the key for the ids map. The wrapper is needed to
     * ensure that the equals method only returns true if the two objects are the same instance
     * and to ensure that the hash code is always the same for the same instance.
     */
    private class ObjectEntry {
        private Object obj;

        /** Instantiates an ObjectEntry wrapper around the specified object*/
        public ObjectEntry(Object obj) {
            this.obj = obj;
        }


        /** Returns true if and only if the objects contained in this wrapper and the other
         * wrapper are the exact same object (same instance, not just equivalent)*/
        @Override
        public boolean equals(Object other) {
            return obj == ((ObjectEntry)other).obj;
        }


        /**
         * Returns the contained object's identityHashCode. Note that identityHashCode values
         * are not guaranteed to be unique from object to object, but the hash code is guaranteed to
         * not change over time for a given instance of an Object.
         */
        @Override
        public int hashCode() {
            return System.identityHashCode(obj);
        }
    }
}

Ich glaube, dass dies eindeutige IDs während der gesamten Laufzeit des Programms sicherstellen sollte. Beachten Sie jedoch, dass Sie dies wahrscheinlich nicht in einer Produktionsanwendung verwenden möchten, da Verweise auf alle Objekte beibehalten werden, für die Sie IDs generieren. Dies bedeutet, dass Objekte, für die Sie eine ID erstellen, niemals mit Müll gesammelt werden.

Da ich dies für Debug-Zwecke verwende, bin ich nicht allzu besorgt darüber, dass Speicher freigegeben wird.

Sie können dies ändern, um das Löschen von Objekten oder das Entfernen einzelner Objekte zu ermöglichen, wenn das Freigeben von Speicher ein Problem darstellt.

NateW
quelle