¿Cómo obtener la identificación única de un objeto que anula hashCode ()?

231

Cuando una clase en Java no anula hashCode () , imprimir una instancia de esta clase da un buen número único.

El Javadoc de Object dice acerca de hashCode () :

Por mucho que sea razonablemente práctico, el método hashCode definido por la clase Object devuelve enteros distintos para objetos distintos.

Pero cuando la clase anula hashCode () , ¿cómo puedo obtener su número único?

ivan_ivanovich_ivanoff
fuente
33
Principalmente por razones de "depuración";) Para poder decir: ¡Ah, el mismo objeto!
ivan_ivanovich_ivanoff
55
Para este propósito, es probable que System.identityHashcode () sea de alguna utilidad. Sin embargo, no confiaría en él para implementar la funcionalidad del código. Si desea identificar objetos de manera única, puede usar AspectJ y tejer código en una identificación única por objeto creado. Sin embargo, más trabajo
Brian Agnew
99
Solo tenga en cuenta que no se garantiza que hashCode sea único. Incluso si la implementación usa la dirección de memoria como el hashCode predeterminado. ¿Por qué no es único? Porque los objetos se recolectan y la memoria se reutiliza.
Igor Krivokon
8
Si desea decidir, si dos objetos son iguales, use == en lugar de hashCode (). No se garantiza que este último sea único, incluso en la implementación original.
Mnementh
66
Ninguna de las respuestas responde a la pregunta real porque se enredan en la discusión de hashCode (), que fue incidental aquí. Si miro las variables de referencia en Eclipse, me muestra un "id = xxx" inmutable único. ¿Cómo llegamos a ese valor mediante programación sin tener que usar nuestro propio generador de identificación? Quiero acceder a ese valor con fines de depuración (registro) para identificar instancias distintas de objetos. ¿Alguien sabe cómo conseguir ese valor?
Chris Westin

Respuestas:

346

System.identityHashCode (yourObject) dará el código hash 'original' de yourObject como un entero. La unicidad no está necesariamente garantizada. La implementación de Sun JVM le dará un valor que está relacionado con la dirección de memoria original para este objeto, pero ese es un detalle de implementación y no debe confiar en él.

EDITAR: Respuesta modificada siguiendo el comentario de Tom a continuación re. direcciones de memoria y objetos en movimiento.

Brian Agnew
fuente
Déjame adivinar: ¿no es único cuando tienes más de 2 ** 32 objetos en la misma JVM? ;) ¿Me puede señalar a algún lugar, donde se describe la no unicidad? Gracias!
ivan_ivanovich_ivanoff
99
No importa cuántos objetos haya o cuánta memoria haya. No se requiere hashCode () ni identityHashCode () para producir un número único.
Alan Moore
12
Brian: No es la ubicación real de la memoria, obtienes una versión revisada de una dirección cuando se calcula por primera vez. En una VM moderna, los objetos se moverán en la memoria.
Tom Hawtin - tackline
2
Entonces, si se crea un objeto en la dirección de memoria 0x2000, la máquina virtual lo mueve y luego se crea otro objeto en 0x2000, ¿tendrán el mismo System.identityHashCode()?
Expiación limitada
14
La unicidad no está garantizada en absoluto ... para una implementación práctica de JVM. La unicidad garantizada no requiere reubicación / compactación por parte del GC, o una estructura de datos grande y costosa para administrar los valores de código hash de los objetos vivos.
Stephen C
28

El javadoc para Object especifica que

Esto se implementa típicamente al convertir la dirección interna del objeto en un número entero, pero el lenguaje de programación JavaTM no requiere esta técnica de implementación.

Si una clase anula hashCode, significa que quiere generar una identificación específica, que (se puede esperar) tendrá el comportamiento correcto.

Puede usar System.identityHashCode para obtener esa identificación para cualquier clase.

Valentin Rocher
fuente
7

hashCode()El método no es para proporcionar un identificador único para un objeto. Más bien digiere el estado del objeto (es decir, los valores de los campos miembros) a un solo entero. Este valor es utilizado principalmente por algunas estructuras de datos basadas en hash como mapas y conjuntos para almacenar y recuperar objetos de manera efectiva.

Si necesita un identificador para sus objetos, le recomiendo que agregue su propio método en lugar de anularlo hashCode. Para este propósito, puede crear una interfaz base (o una clase abstracta) como a continuación.

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

Ejemplo de uso:

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

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

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

¿Quizás esta solución rápida y sucia funcione?

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

    public int hashCode() {
        return uid;
    }
}

Esto también proporciona el número de instancias de una clase que se inicializa.

John Pang
fuente
44
Esto supone que tiene acceso al código fuente de la clase
pablisco
Si no puede acceder al código fuente, simplemente extiéndalo y use la clase extendida. Solución simplemente rápida, fácil y sucia, pero funciona.
John Pang
1
No siempre funciona. La clase podría ser final. Creo que System.identityHashCodees una mejor solución
pablisco
2
Para la seguridad del hilo, se podría usar AtomicLongcomo en esta respuesta .
Evgeni Sergeev
Si la clase es cargada por un cargador de clases diferente, tendrá diferentes variables estáticas UNIQUE_ID, ¿estoy en lo cierto?
cupiqi09
4

Si es una clase que puede modificar, puede declarar una variable de clase static java.util.concurrent.atomic.AtomicInteger nextInstanceId. (Tendrá que darle un valor inicial de la manera obvia). Luego declare una variable de instancia int instanceId = nextInstanceId.getAndIncrement().

Aaron Mansheim
fuente
2

Se me ocurrió esta solución que funciona en mi caso donde tengo objetos creados en múltiples hilos y son serializables:

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

Si está buscando los hashcodetipos de Java cuando hace un .toString()en un objeto, el código subyacente es este:

Integer.toHexString(hashCode())
Frankie
fuente
0

Solo para aumentar las otras respuestas desde un ángulo diferente.

Si desea reutilizar los códigos hash de 'arriba' y derivar nuevos usando el estado inmutable de su clase, entonces una llamada a super funcionará. Si bien esto puede / puede no caer en cascada hasta el objeto (es decir, algunos antepasados ​​pueden no llamar super), le permitirá obtener códigos hash mediante la reutilización.

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

Hay una diferencia entre los retornos hashCode () e identityHashCode (). Es posible que para dos objetos desiguales (probados con ==) o1, o2 hashCode () pueda ser el mismo. Vea el siguiente ejemplo de cómo esto es cierto.

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");
        }
    }
}
Desarrollador Marius Žilėnas
fuente
0

Tuve el mismo problema y no estaba satisfecho con ninguna de las respuestas hasta ahora, ya que ninguna de ellas garantizaba identificaciones únicas.

Yo también quería imprimir ID de objeto para la depuración intencionada. Sabía que debía haber alguna forma de hacerlo, porque en el depurador de Eclipse, especifica ID únicos para cada objeto.

Se me ocurrió una solución basada en el hecho de que el operador "==" para objetos solo devuelve verdadero si los dos objetos son en realidad la misma instancia.

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

Creo que esto debería garantizar identificaciones únicas durante toda la vida del programa. Sin embargo, tenga en cuenta que probablemente no desee usar esto en una aplicación de producción porque mantiene referencias a todos los objetos para los que genera ID. Esto significa que cualquier objeto para el que cree una ID nunca se recolectará como basura.

Como estoy usando esto para propósitos de depuración, no estoy demasiado preocupado con la memoria liberada.

Puede modificar esto para permitir la eliminación de objetos o la eliminación de objetos individuales si le preocupa liberar memoria.

NateW
fuente