【问题标题】:How to get the unique ID of an object which overrides hashCode()?如何获取覆盖 hashCode() 的对象的唯一 ID?
【发布时间】:2010-10-28 22:23:27
【问题描述】:

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

Object 的 Javadoc 中提到了 hashCode()

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

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

【问题讨论】:

  • 主要是出于“调试”的原因;)能够说:啊,同一个对象!
  • 为此,System.identityHashcode() 可能有一些用处。但是,我不会依赖它来实现代码功能。如果您想唯一地标识对象,您可以使用 AspectJ 和 code-weave 在每个创建的对象的唯一 id 中。更多的工作,虽然
  • 请记住,不保证 hashCode 是唯一的。即使实现使用内存地址作为默认的 hashCode。为什么不是独一无二的?因为对象会被垃圾回收,而内存会被重用。
  • 如果你想决定,如果两个对象相同,使用 == 而不是 hashCode()。后者不保证是唯一的,即使在原始实现中也是如此。
  • 没有一个答案能回答真正的问题,因为他们纠结于讨论 hashCode(),这在此处是偶然的。如果我查看 Eclipse 中的引用变量,它会显示一个唯一的不可变“id=xxx”。我们如何在不使用我们自己的 id 生成器的情况下以编程方式获得该值?我希望访问该值以进行调试(记录),以便识别不同的对象实例。有人知道如何获得这个价值吗?

标签: java identity hashcode


【解决方案1】:

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

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

【讨论】:

  • 让我猜猜:当你在同一个 JVM 中有超过 2**32 个对象时,它不是唯一的? ;) 你能指出我描述它的非唯一性的某个地方吗?谢谢!
  • 有多少对象,有多少内存都没有关系。 hashCode() 和 identityHashCode() 都不需要生成唯一编号。
  • Brian:这不是实际的内存位置,您在第一次计算时碰巧得到一个地址的重新散列版本。在现代 VM 中,对象会在内存中移动。
  • 那么如果一个对象是在内存地址0x2000创建的,然后被VM移动了,那么另一个对象是在0x2000创建的,他们会不会有相同的System.identityHashCode()
  • 唯一性根本没有保证 ...对于实际的 JVM 实现。保证唯一性不需要 GC 重新定位/压缩,或者需要一个大而昂贵的数据结构来管理活动对象的哈希码值。
【解决方案2】:

Object 的 javadoc 指定

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

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      public class A {
          static int UNIQUE_ID = 0;
          int uid = ++UNIQUE_ID;
      
          public int hashCode() {
              return uid;
          }
      }
      

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

      【讨论】:

      • 这假设您可以访问该类的源代码
      • 如果您无法访问源代码,只需从它扩展并使用扩展类。简单快速、简单且肮脏的解决方案,但它确实有效。
      • 它并不总是有效。这门课可能是最后的。我认为System.identityHashCode 是一个更好的解决方案
      • 为了线程安全,可以使用AtomicLong,如this answer
      • 如果类是由不同的类加载器加载的,它将有不同的 UNIQUE_ID 静态变量,对吗?
      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

          【解决方案7】:
          // looking for that last hex?
          org.joda.DateTime@57110da6
          

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

          Integer.toHexString(hashCode())
          

          【讨论】:

            【解决方案8】:

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

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

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

            【讨论】:

              【解决方案9】:

              只是为了从不同的角度补充其他答案。

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

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

              【讨论】:

                【解决方案10】:

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

                【讨论】:

                  猜你喜欢
                  • 2010-10-09
                  • 1970-01-01
                  • 2011-11-02
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-06-06
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多