Jak uzyskać unikalny identyfikator obiektu, który przesłania hashCode ()?

231

Gdy klasa w Javie nie zastępuje hashCode () , wydrukowanie instancji tej klasy daje niezłą liczbę.

Javadoc Object mówi o hashCode () :

O ile jest to praktycznie możliwe, metoda hashCode zdefiniowana przez klasę Object zwraca różne liczby całkowite dla różnych obiektów.

Ale kiedy klasa zastępuje hashCode () , w jaki sposób mogę uzyskać jej unikalny numer?

ivan_ivanovich_ivanoff
źródło
33
Głównie z powodów „debugowania”;) Aby móc powiedzieć: Ach, ten sam obiekt!
ivan_ivanovich_ivanoff
5
W tym celu prawdopodobnie wykorzysta System.identityHashcode (). Jednak nie polegałbym na nim w zakresie implementacji funkcjonalności kodu. Jeśli chcesz jednoznacznie identyfikować obiekty, możesz użyć AspectJ i splotu kodu w unikalnym identyfikatorze dla tworzonego obiektu. Więcej pracy
Brian Agnew
9
Pamiętaj tylko, że hashCode NIE gwarantuje, że będzie unikalny. Nawet jeśli implementacja używa adresu pamięci jako domyślnego kodu skrótu. Dlaczego nie jest wyjątkowy? Ponieważ obiekty są usuwane, a pamięć jest ponownie wykorzystywana.
Igor Krivokon
8
Jeśli chcesz zdecydować, czy dwa obiekty są takie same, użyj == zamiast hashCode (). Nie gwarantuje się, że ta ostatnia będzie wyjątkowa, nawet w oryginalnej implementacji.
Mnementh
6
Żadna z odpowiedzi nie odpowiada na prawdziwe pytanie, ponieważ są wplątani w dyskusję hashCode (), co było tu przypadkowe. Jeśli spojrzę na zmienne referencyjne w Eclipse, pokazuje mi to niepowtarzalny niezmienny „id = xxx”. Jak dojść do tej wartości programowo, bez konieczności korzystania z własnego generatora identyfikatorów? Chcę uzyskać dostęp do tej wartości do celów debugowania (rejestrowania) w celu identyfikacji różnych wystąpień obiektów. Czy ktoś wie, jak zdobyć tę wartość?
Chris Westin,

Odpowiedzi:

346

System.identityHashCode (yourObject) poda „oryginalny” kod skrótu yourObject jako liczbę całkowitą. Wyjątkowość niekoniecznie jest gwarantowana. Implementacja Sun JVM da ci wartość związaną z pierwotnym adresem pamięci dla tego obiektu, ale jest to szczegół implementacji i nie powinieneś na nim polegać.

EDYCJA: Odpowiedź zmodyfikowana po komentarzu Toma poniżej re. adresy pamięci i ruchome obiekty.

Brian Agnew
źródło
Niech zgadnę: to nie jest wyjątkowe, jeśli masz więcej niż 2 ** 32 obiektów w tej samej JVM? ;) Czy możesz wskazać mi jakieś miejsce, w którym opisana jest wyjątkowość? Dzięki!
ivan_ivanovich_ivanoff
9
Nie ma znaczenia, ile jest obiektów ani ile pamięci. Ani hashCode (), ani tożsamośćHashCode () nie jest wymagana do utworzenia unikalnego numeru.
Alan Moore
12
Brian: To nie jest rzeczywista lokalizacja pamięci, po obliczeniu dostajesz zmienioną wersję adresu. W nowoczesnej maszynie wirtualnej obiekty będą się poruszać w pamięci.
Tom Hawtin - tackline
2
Więc jeśli obiekt zostanie utworzony pod adresem pamięci 0x2000, to zostanie przeniesiony przez maszynę wirtualną, a następnie inny obiekt zostanie utworzony w 0x2000, czy będą one miały to samo System.identityHashCode()?
Ograniczone Zadośćuczynienie
14
Unikalność wcale nie jest gwarantowana ... dla praktycznej implementacji JVM. Gwarantowana wyjątkowość nie wymaga ani przeniesienia / zagęszczenia przez GC, ani dużej i kosztownej struktury danych do zarządzania wartościami hashcode obiektów aktywnych.
Stephen C
28

Jawadoc dla Object określa to

Zazwyczaj jest to realizowane przez konwersję wewnętrznego adresu obiektu na liczbę całkowitą, ale język programowania JavaTM nie wymaga tej techniki implementacji.

Jeśli klasa zastępuje hashCode, oznacza to, że chce wygenerować określony identyfikator, który (można mieć nadzieję) będzie miał właściwe zachowanie.

Możesz użyć System.identityHashCode, aby uzyskać ten identyfikator dla dowolnej klasy.

Valentin Rocher
źródło
7

hashCode()Metoda nie służy do zapewnienia unikalnego identyfikatora obiektu. Raczej przetwarza stan obiektu (tj. Wartości pól członka) na jedną liczbę całkowitą. Ta wartość jest używana głównie przez niektóre struktury danych oparte na haszowaniu, takie jak mapy i zestawy, w celu skutecznego przechowywania i pobierania obiektów.

Jeśli potrzebujesz identyfikatora dla swoich obiektów, zalecamy dodanie własnej metody zamiast przesłonięcia hashCode. W tym celu możesz utworzyć interfejs podstawowy (lub klasę abstrakcyjną) jak poniżej.

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

Przykładowe użycie:

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

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

    @Override
    public Integer getId() {
        return studentId;
    }
}
ovunccetin
źródło
6

Może to szybkie, brudne rozwiązanie zadziała?

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

    public int hashCode() {
        return uid;
    }
}

Daje to również liczbę instancji zainicjowanej klasy.

John Pang
źródło
4
Zakłada się, że masz dostęp do kodu źródłowego klasy
pablisco
Jeśli nie możesz uzyskać dostępu do kodu źródłowego, po prostu rozszerz go i użyj rozszerzonej klasy. Po prostu szybkie, łatwe i brudne rozwiązanie, ale działa.
John Pang,
1
to nie zawsze działa. Klasa może być ostateczna. Myślę, że System.identityHashCodejest lepszym rozwiązaniem
pablisco
2
Dla bezpieczeństwa wątków można użyć tegoAtomicLong w tej odpowiedzi .
Evgeni Sergeevev,
Jeśli klasa jest ładowana przez inny moduł ładujący, będzie miała różne zmienne statyczne UNIQUE_ID, czy mam rację?
cupiqi09
4

Jeśli jest to klasa, którą możesz zmodyfikować, możesz zadeklarować zmienną klasy static java.util.concurrent.atomic.AtomicInteger nextInstanceId. (Będziesz musiał podać jej wartość początkową w oczywisty sposób.) Następnie zadeklaruj zmienną instancji int instanceId = nextInstanceId.getAndIncrement().

Aaron Mansheim
źródło
2

Wymyśliłem to rozwiązanie, które działa w moim przypadku, w którym mam obiekty utworzone w wielu wątkach i można je serializować:

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
źródło
2
// looking for that last hex?
org.joda.DateTime@57110da6

Jeśli patrzysz na hashcodetypy Java podczas wykonywania .toString()na obiekcie, kod źródłowy jest następujący:

Integer.toHexString(hashCode())
Frankie
źródło
0

Po prostu w celu rozszerzenia innych odpowiedzi pod innym kątem.

Jeśli chcesz ponownie użyć hash (ów) z „powyżej” i uzyskać nowe, używając niezmiennego stanu swojej klasy, wtedy zadzwoni do super. Chociaż może to / nie kaskadować aż do Object (tj. Niektórzy przodkowie mogą nie wywoływać super), pozwoli ci to uzyskać kody hash poprzez ponowne użycie.

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

Istnieje różnica między zwrotami funkcji hashCode () a tożsamością HashCode (). Możliwe jest, że dla dwóch nierównych (przetestowanych ==) obiektów o1, o2 hashCode () może być taki sam. Zobacz poniższy przykład, jak to jest prawdą.

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");
        }
    }
}
Deweloper Marius Žilėnas
źródło
0

Miałem ten sam problem i jak dotąd nie byłem zadowolony z żadnej odpowiedzi, ponieważ żadna z nich nie gwarantowała unikatowych identyfikatorów.

Ja też chciałem wydrukować identyfikatory obiektów w celu debugowania. Wiedziałem, że musi być jakiś sposób, aby to zrobić, ponieważ w debuggerze Eclipse określa unikalne identyfikatory dla każdego obiektu.

Wymyśliłem rozwiązanie oparte na fakcie, że operator „==” dla obiektów zwraca wartość true tylko wtedy, gdy dwa obiekty są w rzeczywistości tym samym wystąpieniem.

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

Uważam, że powinno to zapewnić unikalne identyfikatory przez cały czas trwania programu. Zauważ jednak, że prawdopodobnie nie chcesz tego używać w aplikacji produkcyjnej, ponieważ utrzymuje odniesienia do wszystkich obiektów, dla których generujesz identyfikatory. Oznacza to, że wszelkie obiekty, dla których utworzysz identyfikator, nigdy nie zostaną usunięte.

Ponieważ używam tego do celów debugowania, nie martwię się zbytnio uwalnianiem pamięci.

Możesz to zmodyfikować, aby umożliwić usuwanie obiektów lub usuwanie pojedynczych obiektów, jeśli problemem jest zwolnienie pamięci.

NateW
źródło