【问题标题】:Java Collections: IF the key of Hashmap is Immutable Object then do we need to override hashcode and equals?Java 集合:如果 Hashmap 的键是不可变对象,那么我们是否需要覆盖 hashcode 和 equals?
【发布时间】:2026-01-29 06:05:02
【问题描述】:

我对一个概念感到困惑。有人可以解释一下吗?

问题:如果Hashmap的key是Immutable Object(开发者创建),那么我们需要覆盖hashcode()equals()吗?或者将不可变字段作为键解决了覆盖hashcode()equals() 的问题?

谢谢。

【问题讨论】:

  • 是否可以使用一个键存储一个对象,然后尝试使用一个相同对象但不是同一个对象的键来检索它?
  • 那么如果你不覆盖hashCode和equals,你的map就无法知道你用来检索的key和你存储对象时使用的key是一样的,所以它不会找到您存储的对象。

标签: java collections hashmap equals hashcode


【解决方案1】:

是的。我将在此处引用java.lang.Integer 的示例。如果我们希望有一个整数到对象的(稀疏)映射,我们会使用类似于HashMap<Integer, Object> 的东西。如果我们添加一个条目Integer.valueOf(2)=>"foo",并尝试使用new Integer(2) 检索它,则需要覆盖的哈希码和equals。

【讨论】:

    【解决方案2】:

    试试看:

    public class OverrideIt
    {
        static class MyKey
        {
            public final int i; // immutable
            public MyKey(int i)
            {
                this.i = i;
            }
        }
    
        public static void main(String[] args)
        {
            Map<MyKey, String> map = new HashMap<MyKey, String>();
            map.put(new MyKey(1), "Value");
            String result = map.get(new MyKey(1));
            System.out.println(result);  // null
        }
    }
    

    打印出null,表明我们未能查找我们的值。这是因为MyKey的两个副本不相等且没有相同的hashcode,因为我们没有覆盖.equals()hashcode()

    【讨论】:

    • 对不起,我的错误,它不是不确定的。即使哈希码的结果相同,您也是在使用不同的对象键入。这将始终返回 null。我为我的错误道歉。
    【解决方案3】:

    事实

    1. hashMap 的基本数据结构是Entry[](条目是一种LinkedList)。
    2. 用于定位此数组中位置的键的哈希码
    3. 一旦条目使用哈希码重试,然后 Key 的 Equal 用于选择正确的条目(通过迭代 hasNext)
    4. 默认哈希码为该实例返回唯一的整数值。

    你同意评论部分

     "Is it possible that you'll store an object using one key, 
      and then try to retrieve it using a key which is an identical object,
      but not the same object"
    

    即使两个键具有相同的值,实例也不同。那么根据合同(事实 4),您可能对两个密钥都有不同的哈希码。因此,您将在数组中具有不同的位置(规则 2)

    map.put(new key(1),"first element");

    此处的 key Object 不会被覆盖,因此它将为每个实例返回 unquie 哈希码。 (为了避免过于复杂,假设哈希码返回为 000025 。所以 Entry[25] 是“第一个元素”)

    map.get(新键(1))

    现在这个新键可能会返回哈希码值000017,所以它会尝试从Entry[17]中获取值并返回null(不例外)。

    注意,为了简单起见,我只给出了 000025 和 000017 的示例,实际上 hashmap 会重新访问 hashcode 并根据数组大小进行更改

    到目前为止,我们还没有讨论过 Key 是 Mutable 还是 Immutable 的天气。不管键是可变的还是不可变的

    If you store an object using one key,and then try to retrieve it using a key 
    which is an identical object,but not the same object
    

    您需要覆盖哈希码并确保它返回相同的 Integer ,以便它找到相同的存储桶(数组中的位置)并获取元素。同样适用于从 Entry 中获取正确元素的 equals

    【讨论】:

      【解决方案4】:

      这些问题的类别略有不同。

      hexafraction's answer 一样,如果两个不同的实例可能被认为是相同的,那么拥有不可变实例并不足以让您跳过编写equalshashCode 的步骤。 new Integer(2) 应该始终等于所有其他 new Integer(2),即使对象是不可变的并且实例不同。

      也就是说,有一些“实例控制类”的示例,其中 instance identity 的默认行为就足够了:

      • 枚举实例在编译时创建,每个值一个。 (理论上)没有办法产生任何其他实例。如果没有两个实例相等,则equalshashCode 的默认实现就足够了。 Enum instances aren't compiler-guaranteed to be immutable, but you should treat them as such.

      • 如果保证您的类的实例彼此不同,无论它们是否不可变,您都可以跳过equalshashCode。可以想象一个 Car 对象,其中 CarFactory 生产的每辆 Car 都是不同的。

      • 作为上述的变体,如果您对对象实例化的控制足够严密,以至于总是给相等的表示提供完全相同的实例,那么这可以被认为是足够的:

        public class MyClass {
          private MyClass(int foo) { /* ... */ }
        
          private static final Map<Integer, MyClass> instanceCache = new HashMap<>();
        
          /** Returns an existing MyClass(foo) if present; otherwise, creates one. */
          public synchronized static MyClass create(int foo) {
            // Neither leak-proof or thread-safe. Just demonstrating a concept.
            if (instanceCache.contains(foo)) {
              return instanceCache.get(foo);
            }
            MyClass newMyClass = new MyClass(foo);
            instanceCache.put(foo, newMyClass);
            return newMyClass;
          }
        }
        

      【讨论】:

        最近更新 更多