Come ottenere l'ID univoco di un oggetto che sovrascrive hashCode ()?


231

Quando una classe in Java non sovrascrive hashCode () , la stampa di un'istanza di questa classe fornisce un numero univoco piacevole.

Javadoc of Object dice di hashCode () :

Per quanto ragionevolmente pratico, il metodo hashCode definito dalla classe Object restituisce interi distinti per oggetti distinti.

Ma quando la classe sovrascrive hashCode () , come posso ottenere il suo numero univoco?


33
Principalmente per ragioni di "debugging";) Per poter dire: Ah, stesso oggetto!
ivan_ivanovich_ivanoff

5
A tal fine è probabile che System.identityHashcode () sia di qualche utilità. Non mi baserei su di esso per l'implementazione della funzionalità del codice, tuttavia. Se si desidera identificare gli oggetti in modo univoco, è possibile utilizzare AspectJ e la trama del codice in un ID univoco per oggetto creato. Più lavoro, però
Brian Agnew,

9
Basta tenere presente che hashCode NON è garantito per essere unico. Anche se implementaiton utilizza l'indirizzo di memoria come hashCode predefinito. Perché non è unico? Perché gli oggetti vengono raccolti in modo inutile e la memoria viene riutilizzata.
Igor Krivokon,

8
Se vuoi decidere, se due oggetti sono uguali usa == invece di hashCode (). Quest'ultimo non è garantito per essere unico, anche nell'implementazione originale.
Mnementh,

6
Nessuna delle risposte risponde alla vera domanda perché si aggrovigliano nel discutere di hashCode (), che è stato casuale qui. Se guardo le variabili di riferimento in Eclipse, mi mostra un unico "id = xxx" immutabile. Come possiamo arrivare a quel valore a livello di codice senza dover usare il nostro generatore ID? Voglio accedere a quel valore per scopi di debug (registrazione) al fine di identificare distinte istanze di oggetti. Qualcuno sa come mettere le mani su quel valore?
Chris Westin,

Risposte:


346

System.identityHashCode (yourObject) fornirà il codice hash "originale" di yourObject come numero intero. L'unicità non è necessariamente garantita. L'implementazione di Sun JVM fornirà un valore correlato all'indirizzo di memoria originale per questo oggetto, ma si tratta di un dettaglio di implementazione e non dovresti fare affidamento su di esso.

EDIT: risposta modificata in seguito al commento di Tom di seguito re. indirizzi di memoria e oggetti in movimento.


Fammi indovinare: non è unico, quando hai più di 2 ** 32 oggetti nella stessa JVM? ;) Puoi indicarmi un posto in cui è descritta la non unicità? Grazie!
ivan_ivanovich_ivanoff,

9
Non importa quanti oggetti ci siano o quanta memoria ci sia. Né hashCode () né identityHashCode () è necessario per produrre un numero univoco.
Alan Moore,

12
Brian: Non è l'effettiva posizione della memoria, ti capita di ottenere una versione ridisegnata di un indirizzo al primo calcolo. In una moderna VM gli oggetti si muoveranno nella memoria.
Tom Hawtin - tackline

2
Quindi se un oggetto viene creato all'indirizzo di memoria 0x2000, quindi viene spostato dalla VM, quindi un altro oggetto viene creato su 0x2000, avranno lo stesso System.identityHashCode()?
Espiazione limitata il

14
L'unicità non è affatto garantita ... per un'implementazione pratica di JVM. L'unicità garantita non richiede né ricollocazione / compattazione da parte del GC, né una struttura di dati grande e costosa per la gestione dei valori hashcode degli oggetti live.
Stephen C

28

Il javadoc per Object lo specifica

Questo viene in genere implementato convertendo l'indirizzo interno dell'oggetto in un numero intero, ma questa tecnica di implementazione non è richiesta dal linguaggio di programmazione JavaTM.

Se una classe sovrascrive hashCode, significa che vuole generare un ID specifico, che (si può sperare) avrà il comportamento giusto.

È possibile utilizzare System.identityHashCode per ottenere quell'ID per qualsiasi classe.


7

hashCode()il metodo non è per fornire un identificatore univoco per un oggetto. Digerisce piuttosto lo stato dell'oggetto (ovvero i valori dei campi membro) in un singolo numero intero. Questo valore viene utilizzato principalmente da alcune strutture dati basate su hash come mappe e set per archiviare e recuperare oggetti in modo efficace.

Se hai bisogno di un identificatore per i tuoi oggetti, ti consiglio di aggiungere il tuo metodo invece di sovrascrivere hashCode. A tale scopo, è possibile creare un'interfaccia di base (o una classe astratta) come di seguito.

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

Esempio di utilizzo:

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

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

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

6

Forse questa soluzione rapida e sporca funzionerà?

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

    public int hashCode() {
        return uid;
    }
}

Ciò fornisce anche il numero di istanze di una classe che viene inizializzata.


4
Ciò presuppone che tu abbia accesso al codice sorgente della classe
pablisco,

Se non riesci ad accedere al codice sorgente, estendi semplicemente da esso e usa la classe estesa. Soluzione semplicemente veloce, facile e sporca ma funziona.
John Pang,

1
non sempre funziona. La lezione potrebbe essere definitiva. Penso che System.identityHashCodesia una soluzione migliore
pablisco il

2
Per la sicurezza dei thread, si potrebbe usare AtomicLongcome in questa risposta .
Evgeni Sergeev,

Se la classe viene caricata da un diverso programma di caricamento classi avrà diverse variabili statiche UNIQUE_ID, ho ragione?
Cupiqi09

4

Se è una classe che è possibile modificare, è possibile dichiarare una variabile di classe static java.util.concurrent.atomic.AtomicInteger nextInstanceId. (Dovrai dargli un valore iniziale nel modo ovvio.) Quindi dichiarare una variabile di istanza int instanceId = nextInstanceId.getAndIncrement().


2

Ho trovato questa soluzione che funziona nel mio caso in cui ho oggetti creati su più thread e sono serializzabili:

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

2
// looking for that last hex?
org.joda.DateTime@57110da6

Se stai osservando i hashcodetipi Java quando fai .toString()un oggetto, il codice sottostante è questo:

Integer.toHexString(hashCode())

0

Solo per aumentare le altre risposte da un'angolazione diversa.

Se vuoi riutilizzare gli hashcode da "sopra" e ricavarne di nuovi usando lo stato immutabile della tua classe, allora una chiamata a super funzionerà. Mentre questo può / potrebbe non arrivare a cascata fino all'Oggetto (cioè alcuni antenati potrebbero non chiamare super), ti permetterà di derivare gli hashcode riutilizzandoli.

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

0

C'è una differenza tra i rendimenti hashCode () e identityHashCode (). È possibile che per due oggetti diversi (testato con ==) o1, o2 hashCode () possa essere lo stesso. Vedi l'esempio sotto come questo è vero.

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

0

Ho avuto lo stesso problema e finora non sono stato soddisfatto delle risposte poiché nessuno di loro ha garantito ID univoci.

Anch'io volevo stampare gli ID oggetto per il debug previsto. Sapevo che doveva esserci un modo per farlo, perché nel debugger Eclipse specifica ID univoci per ciascun oggetto.

Ho trovato una soluzione basata sul fatto che l'operatore "==" per gli oggetti ritorna vero solo se i due oggetti sono effettivamente la stessa istanza.

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

Credo che ciò dovrebbe garantire ID univoci per tutta la durata del programma. Si noti, tuttavia, che probabilmente non si desidera utilizzarlo in un'applicazione di produzione perché mantiene i riferimenti a tutti gli oggetti per i quali si generano gli ID. Ciò significa che tutti gli oggetti per i quali si crea un ID non verranno mai raccolti.

Dal momento che sto usando questo per scopi di debug, non mi preoccupo troppo della memoria liberata.

È possibile modificarlo per consentire la cancellazione di oggetti o la rimozione di singoli oggetti se liberare memoria è un problema.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.