Как получить уникальный идентификатор объекта, который переопределяет hashCode ()?

231

Когда класс в Java не переопределяет hashCode () , печать экземпляра этого класса дает хороший уникальный номер.

Javadoc объекта говорит о hashCode () :

Насколько это целесообразно, метод hashCode, определенный классом Object, возвращает разные целые числа для разных объектов.

Но когда класс переопределяет hashCode () , как я могу получить его уникальный номер?

ivan_ivanovich_ivanoff
источник
33
Главным образом по причинам «отладки»;) Чтобы можно было сказать: Ах, тот же объект!
ivan_ivanovich_ivanoff
5
Для этой цели System.identityHashcode () может быть полезным. Однако я бы не стал полагаться на это для реализации функциональности кода. Если вы хотите уникально идентифицировать объекты, вы можете использовать AspectJ и weave кода в уникальном идентификаторе для каждого созданного объекта. Больше работы, хотя
Брайан Агнью
9
Просто помните, что hashCode НЕ гарантированно уникален. Даже если реализация использует адрес памяти в качестве хэш-кода по умолчанию. Почему это не уникально? Потому что объекты собирают мусор, а память используется повторно.
Игорь Кривокон
8
Если вы хотите решить, являются ли два объекта одинаковыми, используйте == вместо hashCode (). Последнее не гарантируется быть уникальным, даже в оригинальной реализации.
Мнемент
6
Ни один из ответов не отвечает на реальный вопрос, потому что они запутались в обсуждении hashCode (), который был случайным здесь. Если я посмотрю на ссылочные переменные в Eclipse, он покажет мне уникальную неизменную «id = xxx». Как мы можем получить это значение программно, не используя собственный генератор идентификаторов? Я хочу получить доступ к этому значению для целей отладки (регистрации), чтобы идентифицировать отдельные экземпляры объектов. Кто-нибудь знает, как достать это значение?
Крис Вестин

Ответы:

346

System.identityHashCode (yourObject) выдаст «оригинальный» хеш-код вашего объекта в виде целого числа. Уникальность не обязательно гарантирована. Реализация Sun JVM даст вам значение, которое связано с исходным адресом памяти для этого объекта, но это деталь реализации, и вам не следует полагаться на нее.

РЕДАКТИРОВАТЬ: Ответ изменен после комментария Тома ниже re. адреса памяти и движущиеся объекты.

Брайан Агнью
источник
Позвольте мне угадать: это не уникально, когда у вас есть более 2 ** 32 объектов в одной JVM? ;) Можете ли вы указать мне на какое-то место, где описывается неединственность? Thanx!
ivan_ivanovich_ivanoff
9
Неважно, сколько объектов или сколько памяти. Для создания уникального числа не требуется ни hashCode (), ни identityHashCode ().
Алан Мур
12
Брайан: Это не фактическая область памяти, вы случайно получаете перефразированную версию адреса при первом вычислении. В современной ВМ объекты будут перемещаться в памяти.
Том Хотин - tackline
2
Таким образом, если объект создается по адресу памяти 0x2000, затем перемещается виртуальной машиной, тогда другой объект создается по адресу 0x2000, будут ли они такими же System.identityHashCode()?
Ограниченное искупление
14
Уникальность вообще не гарантируется ... для практической реализации JVM. Гарантированная уникальность не требует либо перемещения / сжатия GC, либо большой и дорогой структуры данных для управления значениями хэш-кода живых объектов.
Стивен С.
28

Javadoc для объекта указывает, что

Обычно это реализуется путем преобразования внутреннего адреса объекта в целое число, но этот метод реализации не требуется языком программирования JavaTM.

Если класс переопределяет hashCode, это означает, что он хочет сгенерировать определенный идентификатор, который будет (можно надеяться) иметь правильное поведение.

Вы можете использовать System.identityHashCode, чтобы получить этот идентификатор для любого класса.

Валентин Роше
источник
7

hashCode()Метод не для предоставления уникального идентификатора для объекта. Он скорее переваривает состояние объекта (то есть значения полей-членов) в одно целое число. Это значение в основном используется некоторыми структурами данных на основе хеша, такими как карты и наборы, для эффективного хранения и извлечения объектов.

Если вам нужен идентификатор для ваших объектов, я рекомендую вам добавить собственный метод вместо переопределения hashCode. Для этого вы можете создать базовый интерфейс (или абстрактный класс), как показано ниже.

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

Пример использования:

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

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

    @Override
    public Integer getId() {
        return studentId;
    }
}
ovunccetin
источник
6

Может быть, это быстрое, грязное решение подойдет?

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

    public int hashCode() {
        return uid;
    }
}

Это также дает номер экземпляра класса, который инициализируется.

Джон Панг
источник
4
Это предполагает, что у вас есть доступ к исходному коду класса
pablisco
Если вы не можете получить доступ к исходному коду, просто расширьте его и используйте расширенный класс. Просто быстрое, простое и грязное решение, но оно работает.
Джон Пан
1
это не всегда работает. Класс может быть окончательным. Я думаю, что System.identityHashCodeэто лучшее решение
Паблиско
2
Для безопасности потока можно использовать AtomicLongкак в этом ответе .
Евгений Сергеев
Если класс загружается другим загрузчиком классов, у него будут разные статические переменные UNIQUE_ID, я прав?
cupiqi09
4

Если это класс, который вы можете изменить, вы можете объявить переменную класса static java.util.concurrent.atomic.AtomicInteger nextInstanceId. (Вы должны будете дать ему начальное значение очевидным способом.) Затем объявите переменную экземпляра int instanceId = nextInstanceId.getAndIncrement().

Аарон Мансхайм
источник
2

Я пришел с этим решением, которое работает в моем случае, когда у меня есть объекты, созданные в нескольких потоках и сериализуемые:

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

Если вы смотрите на hashcodeтипы Java, когда вы делаете .toString()объект, базовый код выглядит так:

Integer.toHexString(hashCode())
Фрэнки
источник
0

Просто чтобы дополнить другие ответы под другим углом.

Если вы хотите повторно использовать хэш-коды из «выше» и получать новые, используя неизменяемое состояние вашего класса, тогда вызов super будет работать. Хотя это может / не может каскадно доходить до Object (т.е. некоторые предки могут не вызывать super), это позволит вам получить хеш-коды путем повторного использования.

@Override
public int hashCode() {
    int ancestorHash = super.hashCode();
    // now derive new hash from ancestorHash plus immutable instance vars (id fields)
}
Глен Бест
источник
0

Существует разница между возвращаемыми значениями hashCode () и identityHashCode (). Возможно, что для двух неравных (протестированных с ==) объектов o1, o2 hashCode () может быть одинаковым. Посмотрите пример ниже, как это правда.

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

У меня была та же проблема, и я не был удовлетворен ни одним из ответов до сих пор, так как ни один из них не гарантировал уникальные идентификаторы.

Я тоже хотел напечатать идентификаторы объектов для отладки. Я знал, что должен быть какой-то способ сделать это, потому что в отладчике Eclipse он определяет уникальные идентификаторы для каждого объекта.

Я придумал решение, основанное на том факте, что оператор "==" для объектов возвращает значение true, только если два объекта на самом деле являются одним и тем же экземпляром.

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

Я считаю, что это должно обеспечить уникальные идентификаторы на протяжении всей жизни программы. Обратите внимание, однако, что вы, вероятно, не хотите использовать это в производственном приложении, потому что оно поддерживает ссылки на все объекты, для которых вы генерируете идентификаторы. Это означает, что любые объекты, для которых вы создаете ID, никогда не будут собираться мусором.

Так как я использую это в целях отладки, я не слишком обеспокоен освобождением памяти.

Вы можете изменить это, чтобы разрешить очистку Объектов или удаление отдельных объектов, если освобождение памяти является проблемой.

NateW
источник