Como obter o ID exclusivo de um objeto que substitui hashCode ()?

231

Quando uma classe em Java não substitui hashCode () , imprimir uma instância dessa classe fornece um número único e agradável.

O Javadoc de Object diz sobre hashCode () :

Tanto quanto for razoavelmente prático, o método hashCode definido pela classe Object retorna números inteiros distintos para objetos distintos.

Mas quando a classe substitui hashCode () , como posso obter seu número exclusivo?

ivan_ivanovich_ivanoff
fonte
33
Principalmente por razões de 'depuração';) Ser capaz de dizer: Ah, mesmo objeto!
Ivan_ivanovich_ivanoff
5
Para esse fim, é provável que o System.identityHashcode () seja útil. No entanto, eu não confiaria nisso para implementar a funcionalidade do código. Se você deseja identificar objetos exclusivamente, você pode usar o AspectJ e tecer código em um ID exclusivo por objeto criado. Mais trabalho, porém
Brian Agnew
9
Lembre-se de que NÃO é garantido que o hashCode seja único. Mesmo que a implementação use o endereço de memória como o hashCode padrão. Por que não é único? Como os objetos são coletados de lixo e a memória é reutilizada.
Igor Krivokon
8
Se você quiser decidir, se dois objetos forem iguais, use == em vez de hashCode (). Não é garantido que este último seja único, mesmo na implementação original.
Mnementh
6
Nenhuma das respostas responde à pergunta real porque elas se envolvem na discussão de hashCode (), que foi incidental aqui. Se eu olhar para as variáveis ​​de referência no Eclipse, ele me mostra um imutável "id = xxx" exclusivo. Como chegamos a esse valor programaticamente sem precisar usar nosso próprio gerador de identificação? Quero acessar esse valor para fins de depuração (log), a fim de identificar instâncias distintas de objetos. Alguém sabe como colocar esse valor em suas mãos?
22715 Chris

Respostas:

346

System.identityHashCode (yourObject) fornecerá o código hash 'original' do yourObject como um número inteiro. Exclusividade não é necessariamente garantida. A implementação da Sun JVM fornecerá um valor relacionado ao endereço de memória original desse objeto, mas esse é um detalhe da implementação e você não deve confiar nele.

EDIT: Resposta modificada após o comentário de Tom abaixo re. endereços de memória e objetos em movimento.

Brian Agnew
fonte
Deixe-me adivinhar: não é único, quando você tem mais de 2 ** 32 objetos na mesma JVM? ;) Você pode me apontar para algum lugar onde a não-singularidade é descrita? Thanx!
Ivan_ivanovich_ivanoff
9
Não importa quantos objetos existem ou quanta memória existe. Nem hashCode () nem identityHashCode () são necessários para produzir um número exclusivo.
2726 Alan Moore
12
Brian: Não é a localização real da memória; você recebe uma versão reformulada de um endereço quando computado pela primeira vez. Em uma VM moderna, os objetos se movimentam na memória.
Tom Hawtin - tackline
2
Portanto, se um objeto for criado no endereço de memória 0x2000 e, em seguida, for movido pela VM, outro objeto será criado em 0x2000, eles terão o mesmo System.identityHashCode()?
Expiação limitada
14
A exclusividade não é garantida ... para uma implementação prática da JVM. A exclusividade garantida não requer realocação / compactação pelo GC ou uma estrutura de dados grande e cara para gerenciar os valores de código hash de objetos ativos.
Stephen C
28

O javadoc para Object especifica que

Isso geralmente é implementado convertendo o endereço interno do objeto em um número inteiro, mas essa técnica de implementação não é requerida pela linguagem de programação JavaTM.

Se uma classe substitui o hashCode, significa que deseja gerar um ID específico, o que (pode-se esperar) terá o comportamento correto.

Você pode usar System.identityHashCode para obter esse ID para qualquer classe.

Valentin Rocher
fonte
7

hashCode()O método não é para fornecer um identificador exclusivo para um objeto. Ele digere o estado do objeto (ou seja, valores dos campos membros) em um único número inteiro. Esse valor é usado principalmente por algumas estruturas de dados baseadas em hash, como mapas e conjuntos, para armazenar e recuperar objetos com eficiência.

Se você precisar de um identificador para seus objetos, recomendo que você adicione seu próprio método em vez de substituir hashCode. Para esse fim, você pode criar uma interface base (ou uma classe abstrata) como abaixo.

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

Exemplo 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
fonte
6

Talvez esta solução rápida e suja funcione?

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

    public int hashCode() {
        return uid;
    }
}

Isso também fornece o número de instâncias de uma classe sendo inicializada.

John Pang
fonte
4
Isso pressupõe que você tem acesso ao código-fonte da classe
pablisco
Se você não puder acessar o código-fonte, apenas estenda-o e use a classe estendida. Solução simplesmente rápida, fácil e suja, mas funciona.
John Pang
1
nem sempre funciona. A aula pode ser final. Eu acho que System.identityHashCodeé uma solução melhor
pablisco
2
Para segurança da linha, pode-se usar AtomicLongcomo nesta resposta .
Evgeni Sergeev
Se a classe for carregada por um carregador de classes diferente, ela terá variáveis ​​estáticas UNIQUE_ID diferentes, estou correto?
cupiqi09 4/03
4

Se é uma classe que você pode modificar, você pode declarar uma variável de classe static java.util.concurrent.atomic.AtomicInteger nextInstanceId. (Você precisará atribuir um valor inicial da maneira óbvia.) Em seguida, declare uma variável de instância int instanceId = nextInstanceId.getAndIncrement().

Aaron Mansheim
fonte
2

Eu vim com esta solução que funciona no meu caso, onde tenho objetos criados em vários threads e são serializáveis:

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

Se você estiver pesquisando os hashcodetipos Java ao executar um .toString()em um objeto, o código subjacente é este:

Integer.toHexString(hashCode())
Frankie
fonte
0

Apenas para aumentar as outras respostas de um ângulo diferente.

Se você deseja reutilizar hashcode (s) de 'above' e obter novos usando o estado imutável da sua classe, uma chamada para super funcionará. Embora isso possa / não ocorra em cascata até o Object (ou seja, alguns ancestrais podem não chamar de super), ele permitirá que você obtenha códigos de hash por reutilização.

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

Há uma diferença entre os retornos hashCode () e identityHashCode (). É possível que, para dois objetos desiguais (testados com ==) o1, o2 hashCode () possa ser o mesmo. Veja o exemplo abaixo como isso é verdade.

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

Eu tive o mesmo problema e não estava satisfeito com nenhuma das respostas até agora, pois nenhuma delas garantiu IDs únicos.

Eu também queria imprimir IDs de objeto para depuração proposta. Eu sabia que devia haver alguma maneira de fazer isso, porque no depurador Eclipse, ele especifica IDs exclusivos para cada objeto.

Eu vim com uma solução baseada no fato de que o operador "==" para objetos só retorna true se os dois objetos são realmente a mesma instância.

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

Acredito que isso deve garantir IDs únicos durante toda a vida útil do programa. Observe, no entanto, que você provavelmente não deseja usá-lo em um aplicativo de produção, pois ele mantém referências a todos os objetos para os quais você gera IDs. Isso significa que quaisquer objetos para os quais você cria um ID nunca serão coletados como lixo.

Como estou usando isso para fins de depuração, não estou muito preocupado com a memória sendo liberada.

Você pode modificar isso para permitir a limpeza de objetos ou a remoção de objetos individuais se a liberação de memória for uma preocupação.

NateW
fonte