如何获取覆盖hashCode()的对象的唯一ID?


231

如果Java中的类未覆盖hashCode(),则打印该类的实例将提供一个不错的唯一数字。

Object的Javadoc谈到了hashCode()

在合理可行的范围内,由Object类定义的hashCode方法确实为不同的对象返回不同的整数。

但是,当类重写hashCode()时,如何获得其唯一编号?


33
主要是因为“调试”原因;)可以说:啊,同一个对象!
ivan_ivanovich_ivanoff 2009年

5
为此,可能会使用System.identityHashcode()。但是,我不会依靠它来实现代码功能。如果要唯一地标识对象,则可以使用AspectJ并在每个创建的对象的唯一ID中进行代码编织。不过,还有更多工作
要做

9
只要记住,hashCode不能保证是唯一的。即使实现使用内存地址作为默认的hashCode。为什么不是唯一的?因为对象会被垃圾回收,并且内存会被重用。
Igor Krivokon,2009年

8
如果要确定两个对象是否相同,请使用==代替hashCode()。即使在原始实现中,也不能保证后者是唯一的。
Mnementh,2009年

6
没有一个答案可以回答真正的问题,因为在讨论hashCode()时,它们会很纠结,这在此是偶然的。如果我在Eclipse中查看参考变量,它将向我展示一个唯一的不可变的“ id = xxx”。我们如何以编程方式获得该值而不必使用我们自己的id生成器?我想出于调试目的(记录)访问该值,以标识对象的不同实例。有人知道如何获得这一价值吗?
克里斯·威斯汀

Answers:


346

System.identityHashCode(yourObject)将给出yourObject的“原始”哈希码为整数。唯一性不一定得到保证。Sun JVM实现将为您提供一个与此对象的原始内存地址相关的值,但这是实现的详细信息,您不应依赖它。

编辑:答案下面汤姆的评论重新修改。内存地址和移动对象。


让我猜测:当您在同一JVM中有2 ** 32个以上的对象时,它不是唯一的吗?;)您能指出我所描述的非唯一性的地方吗?谢谢!
ivan_ivanovich_ivanoff 2009年

9
有多少个对象或有多少内存都没有关系。不需要hashCode()或identityHashCode()即可生成唯一的数字。
艾伦·摩尔

12
布莱恩:这不是实际的内存位置,您在第一次计算时碰巧得到了地址的重新映射版本。在现代VM中,对象将在内存中移动。
汤姆·霍顿

2
因此,如果在内存地址0x2000处创建了一个对象,然后由VM移动了该对象,那么在0x2000处创建了另一个对象,它们是否具有相同的对象System.identityHashCode()
有限赎罪者2013年

14
对于实际的JVM实现,根本无法保证唯一性。保证唯一性不需要GC进行重新定位/压缩,也不需要大型且昂贵的数据结构来管理活动对象的哈希码值。
史蒂芬C

28

对象的javadoc指定

通常通过将对象的内部地址转换为整数来实现,但是JavaTM编程语言不需要此实现技术。

如果一个类覆盖了hashCode,则意味着它想要生成一个特定的ID,该ID将(具有希望的)行为正确。

您可以使用System.identityHashCode来获取任何类的ID。


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

6

也许这种快速,肮脏的解决方案会起作用?

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

    public int hashCode() {
        return uid;
    }
}

这也给出了正在初始化的类的实例数。


4
假定您有权访问该类的源代码
pablisco 2015年

如果您无法访问源代码,只需对其进行扩展并使用扩展的类。简单,快速,简单和肮脏的解决方案,但它可以工作。
John Pang

1
它并不总是有效。全班可能是最后的。我认为这System.identityHashCode是一个更好的解决方案
pablisco 2015年

2
为了线程安全,可以AtomicLong此答案中使用
Evgeni Sergeev

如果该类是由其他类加载器加载的,它将具有不同的UNIQUE_ID静态变量,对吗?
cupiqi09

4

如果是可以修改的类,则可以声明一个class variable 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的调用将起作用。尽管这可能/可能不会一直到对象的级联(即某些祖先可能没有调用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

我遇到了同样的问题,到目前为止,对任何答案都不满意,因为它们都不能保证唯一的ID。

我也想打印用于调试目的的对象ID。我知道必须采取某种方法,因为在Eclipse调试器中,它为每个对象指定唯一的ID。

我提出了一个基于以下事实的解决方案:对象的==运算符仅在两个对象实际上是同一实例时才返回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。但是请注意,您可能不想在生产应用程序中使用它,因为它维护对为其生成ID的所有对象的引用。这意味着您将为其创建ID的任何对象都不会被垃圾回收。

因为我将其用于调试目的,所以我不太担心释放内存。

如果需要释放内存,则可以修改此选项以允许清除对象或删除单个对象。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.