Comment obtenir l'ID unique d'un objet qui remplace hashCode ()?

231

Lorsqu'une classe en Java ne remplace pas hashCode () , l'impression d'une instance de cette classe donne un joli numéro unique.

Le Javadoc de l'objet dit à propos de hashCode () :

Autant qu'il est raisonnablement pratique, la méthode hashCode définie par la classe Object renvoie des entiers distincts pour des objets distincts.

Mais lorsque la classe remplace hashCode () , comment puis-je obtenir son numéro unique?

ivan_ivanovich_ivanoff
la source
33
Surtout pour des raisons de 'débogage';) Pour pouvoir dire: Ah, même objet!
ivan_ivanovich_ivanoff
5
À cette fin, le System.identityHashcode () est probablement d'une certaine utilité. Cependant, je ne compterais pas dessus pour implémenter la fonctionnalité de code. Si vous souhaitez identifier les objets de manière unique, vous pouvez utiliser AspectJ et le code-weave dans un identifiant unique par objet créé. Plus de travail, cependant
Brian Agnew
9
Gardez juste à l'esprit que hashCode n'est PAS garanti d'être unique. Même si l'implémentation utilise l'adresse mémoire comme hashCode par défaut. Pourquoi n'est-il pas unique? Parce que les objets sont récupérés et la mémoire est réutilisée.
Igor Krivokon
8
Si vous voulez décider, si deux objets sont identiques, utilisez == au lieu de hashCode (). Ce dernier n'est pas garanti d'être unique, même dans la mise en œuvre d'origine.
Mnementh
6
Aucune des réponses ne répond à la vraie question car elles s'emmêlent dans la discussion de hashCode (), qui était accessoire ici. Si je regarde les variables de référence dans Eclipse, cela me montre un "id = xxx" immuable unique. Comment pouvons-nous atteindre cette valeur par programme sans avoir à utiliser notre propre générateur d'ID? Je veux accéder à cette valeur à des fins de débogage (journalisation) afin d'identifier des instances distinctes d'objets. Quelqu'un sait-il comment mettre la main sur cette valeur?
Chris Westin

Réponses:

346

System.identityHashCode (yourObject) donnera le code de hachage «original» de yourObject sous forme d'entier. L'unicité n'est pas nécessairement garantie. L'implémentation Sun JVM vous donnera une valeur qui est liée à l'adresse mémoire d'origine de cet objet, mais c'est un détail d'implémentation et vous ne devriez pas vous y fier.

EDIT: Réponse modifiée suite au commentaire de Tom ci-dessous re. adresses mémoire et objets en mouvement.

Brian Agnew
la source
Laissez-moi deviner: ce n'est pas unique, lorsque vous avez plus de 2 ** 32 objets dans la même JVM? ;) Pouvez-vous m'indiquer un endroit où la non-unicité est décrite? Merci!
ivan_ivanovich_ivanoff
9
Peu importe le nombre d'objets ou la quantité de mémoire. Ni hashCode () ni identityHashCode () ne sont nécessaires pour produire un numéro unique.
Alan Moore
12
Brian: Ce n'est pas l'emplacement réel de la mémoire, vous obtenez une version remaniée d'une adresse lors du premier calcul. Dans une machine virtuelle moderne, les objets se déplaceront en mémoire.
Tom Hawtin - tackline
2
Donc, si un objet est créé à l'adresse mémoire 0x2000, puis est déplacé par la machine virtuelle, puis un autre objet est créé à 0x2000, auront-ils la même chose System.identityHashCode()?
Expiation limitée du
14
L'unicité n'est pas du tout garantie ... pour une implémentation JVM pratique. L'unicité garantie ne nécessite aucune relocalisation / compactage par le GC, ou une structure de données volumineuse et coûteuse pour gérer les valeurs de code de hachage des objets vivants.
Stephen C
28

Le javadoc pour Object spécifie que

Ceci est généralement implémenté en convertissant l'adresse interne de l'objet en un entier, mais cette technique d'implémentation n'est pas requise par le langage de programmation JavaTM.

Si une classe remplace hashCode, cela signifie qu'elle veut générer un identifiant spécifique, qui (on peut l'espérer) aura le bon comportement.

Vous pouvez utiliser System.identityHashCode pour obtenir cet identifiant pour n'importe quelle classe.

Valentin Rocher
la source
7

hashCode()n'est pas pour fournir un identifiant unique pour un objet. Il résume plutôt l'état de l'objet (c'est-à-dire les valeurs des champs membres) en un seul entier. Cette valeur est principalement utilisée par certaines structures de données basées sur le hachage comme les cartes et les ensembles pour stocker et récupérer efficacement des objets.

Si vous avez besoin d'un identifiant pour vos objets, je vous recommande d'ajouter votre propre méthode au lieu de remplacer hashCode. Pour cela, vous pouvez créer une interface de base (ou une classe abstraite) comme ci-dessous.

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

Exemple d'utilisation:

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

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

    @Override
    public Integer getId() {
        return studentId;
    }
}
ovunccétine
la source
6

Peut-être que cette solution rapide et sale fonctionnera?

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

    public int hashCode() {
        return uid;
    }
}

Cela donne également le nombre d'instances d'une classe en cours d'initialisation.

John Pang
la source
4
Cela suppose que vous avez accès au code source de la classe
pablisco
Si vous ne pouvez pas accéder au code source, étendez-le simplement et utilisez la classe étendue. Solution simplement rapide, facile et sale mais cela fonctionne.
John Pang
1
ça ne marche pas toujours. La classe pourrait être finale. Je pense que System.identityHashCodec'est une meilleure solution
pablisco
2
Pour la sécurité des threads, on pourrait utiliser AtomicLongcomme dans cette réponse .
Evgeni Sergeev
Si la classe est chargée par un chargeur de classe différent, elle aura différentes variables statiques UNIQUE_ID, ai-je raison?
cupiqi09
4

S'il s'agit d'une classe que vous pouvez modifier, vous pouvez déclarer une variable de classe static java.util.concurrent.atomic.AtomicInteger nextInstanceId. (Vous devrez lui donner une valeur initiale de la manière la plus évidente.) Ensuite, déclarez une variable d'instance int instanceId = nextInstanceId.getAndIncrement().

Aaron Mansheim
la source
2

J'ai trouvé cette solution qui fonctionne dans mon cas où j'ai des objets créés sur plusieurs threads et qui sont sérialisables:

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
la source
2
// looking for that last hex?
org.joda.DateTime@57110da6

Si vous examinez les hashcodetypes Java lorsque vous effectuez une opération .toString()sur un objet, le code sous-jacent est le suivant:

Integer.toHexString(hashCode())
Frankie
la source
0

Juste pour augmenter les autres réponses sous un angle différent.

Si vous souhaitez réutiliser des codes de hachage à partir de «ci-dessus» et en dériver de nouveaux en utilisant l'état immuable de votre classe, alors un appel à super fonctionnera. Bien que cela puisse / ne puisse pas monter en cascade jusqu'à Object (c'est-à-dire qu'un ancêtre peut ne pas appeler super), cela vous permettra de dériver des codes de hachage par réutilisation.

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

Il existe une différence entre les retours hashCode () et identityHashCode (). Il est possible que pour deux objets inégaux (testés avec ==) o1, o2 hashCode () puisse être le même. Voir l'exemple ci-dessous comment cela est vrai.

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");
        }
    }
}
Développeur Marius Žilėnas
la source
0

J'ai eu le même problème et je n'ai été satisfait d'aucune des réponses jusqu'à présent, car aucune d'entre elles ne garantissait des identifiants uniques.

Moi aussi, je voulais imprimer les ID d'objet pour le débogage à dessein. Je savais qu'il devait y avoir un moyen de le faire, car dans le débogueur Eclipse, il spécifie des ID uniques pour chaque objet.

J'ai trouvé une solution basée sur le fait que l'opérateur "==" pour les objets ne retourne vrai que si les deux objets sont en fait la même instance.

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);
        }
    }
}

Je pense que cela devrait garantir des identifiants uniques tout au long de la durée de vie du programme. Notez cependant que vous ne souhaiterez probablement pas l'utiliser dans une application de production car elle conserve des références à tous les objets pour lesquels vous générez des ID. Cela signifie que les objets pour lesquels vous créez un ID ne seront jamais récupérés.

Étant donné que j'utilise cela à des fins de débogage, je ne suis pas trop préoccupé par la mémoire libérée.

Vous pouvez modifier cela pour permettre d'effacer des objets ou de supprimer des objets individuels si la libération de mémoire est un problème.

NateW
la source