繁体   English   中英

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

[英]How to get the unique ID of an object which overrides hashCode()?

当 Java 中的 class 不覆盖hashCode()时,打印此 class 的实例会给出一个很好的唯一编号。

Object 的 Javadoc 关于hashCode()说:

在相当实用的情况下,class Object 定义的 hashCode 方法确实会为不同的对象返回不同的整数。

但是当 class 覆盖hashCode()时,我怎样才能得到它的唯一编号?

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

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

Object 的 javadoc 指定

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

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

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

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

也许这个快速而肮脏的解决方案会奏效?

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

    public int hashCode() {
        return uid;
    }
}

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

如果它是一个可以修改的类,则可以声明一个类变量static java.util.concurrent.atomic.AtomicInteger nextInstanceId (你必须以明显的方式给它一个初始值。)然后声明一个实例变量int instanceId = nextInstanceId.getAndIncrement()

我想出了这个解决方案,它适用于我在多个线程上创建对象并且可序列化的情况:

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

如果您在对象上执行.toString()时查看hashcode Java 类型,则底层代码是这样的:

Integer.toHexString(hashCode())

我遇到了同样的问题,并且对到目前为止的任何答案都不满意,因为它们都不能保证唯一的 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 的任何对象都不会被垃圾回收。

由于我将其用于调试目的,因此我不太关心释放的内存。

如果释放内存是一个问题,您可以修改它以允许清除对象或删除单个对象。

只是为了从不同的角度增加其他答案。

如果您想重用来自“above”的哈希码并使用您的类的不可变状态派生新的哈希码,则调用 super 将起作用。 虽然这可能/可能不会一直级联到 Object(即某些祖先可能不会调用 super),但它允许您通过重用来派生哈希码。

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

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

由于Object.hashCode()System.identityHashCode()不提供保证唯一的 ID,我认为正确的答案是生成 UUID 或 GUID:

java.util.UUID.randomUUID()

这个答案是线程安全的,并且可以跨不同的虚拟机工作。

例如, Identifiable class 可以扩展如下,为任何 class 提供唯一 ID:

public abstract class Identifiable {
    public final UUID id = UUID.randomUUID();
}

...

public class Example extends Identifiable {}

...

public static void main(String[] args) {

    Example example1 = new Example();
    Example example2 = new Example();

    example1.id.toString(); // e.g. 8308798d-7cec-427d-b7f8-7be762f3b5c7
    example1.id.equals(example1.id); // true
    example1.id.equals(example2.id); // false
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM