256 votes

Comment obtenir l'identifiant unique d'un objet qui surcharge hashCode() ?

Lorsqu'une classe en Java ne surcharge pas la fonction hashCode() , L'impression d'une instance de cette classe permet d'obtenir un numéro unique.

La Javadoc d'Object dit à peu près ceci hashCode() :

Dans la mesure du possible, la méthode hashCode définie par la classe Object renvoie des nombres entiers distincts pour des objets distincts.

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

38 votes

Principalement pour des raisons de "débogage" ;) Pour pouvoir dire : Ah, même objet !

6 votes

À cette fin, la fonction System.identityHashcode() est probablement utile. Cependant, je ne m'y fierais pas pour mettre en œuvre une fonctionnalité de code. Si vous souhaitez identifier les objets de manière unique, vous pouvez utiliser AspectJ et insérer un identifiant unique pour chaque objet créé. Plus de travail, cependant

9 votes

Gardez à l'esprit que l'unicité du hashCode n'est PAS garantie. Même si l'implémentation utilise l'adresse mémoire comme code de hachage par défaut. Pourquoi n'est-il pas unique ? Parce que les objets sont ramassés et que la mémoire est réutilisée.

5voto

Howard Swope Points 11

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

4voto

Aaron Mansheim Points 325

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 qui soit.) Déclarez ensuite une variable d'instance int instanceId = nextInstanceId.getAndIncrement() .

2voto

NateW Points 98

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

Je voulais également imprimer les identifiants des objets à des fins de débogage. Je savais qu'il devait y avoir un moyen de le faire, car dans le débogueur Eclipse, il est spécifié 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 voudrez probablement pas utiliser cette méthode dans une application de production, car elle maintient des références à tous les objets pour lesquels vous générez des identifiants. Cela signifie que tous les objets pour lesquels vous créez un identifiant ne seront jamais ramassés.

Comme je l'utilise à des fins de débogage, je ne suis pas trop préoccupé par la libération de la mémoire.

Vous pouvez modifier cette procédure pour permettre l'effacement des objets ou la suppression d'objets individuels si la libération de la mémoire est une préoccupation.

0voto

Willmore Points 2404

Il existe une différence entre les retours de hashCode() et de identityHashCode(). Il est possible que pour deux objets inégaux (testés avec ==) o1, o2 hashCode() soit le même. L'exemple ci-dessous montre que c'est le cas.

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

0voto

Glen Best Points 11455

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

Si vous souhaitez réutiliser le(s) code(s) de hachage d'en haut et en dériver de nouveaux en utilisant l'état immuable de votre classe, alors un appel à super fonctionnera. Bien que cela puisse ou non se répercuter 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)
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X