簡體   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