【问题标题】:Why are keys immutable in Java?为什么键在 Java 中是不可变的?
【发布时间】:2015-12-03 08:23:33
【问题描述】:

为这个相当幼稚的问题道歉,但我相信我自己的回答是幼稚的。我认为键(在 HashTables 中)是不可变的,因为我们不想以某种方式意外更改键,从而弄乱 HashTable 的排序。这是一个正确的解释吗?如果是这样,如何更正确?

【问题讨论】:

    标签: java hashtable


    【解决方案1】:

    HashTable.put 期间,键被散列并且它的值被存储在基于散列的多个存储桶(它们是键值对的列表)之一中,例如:

    bucket[key.hashcode() % numberOfBuckets].add(key, value)
    

    如果密钥的 hashcode 在插入后发生更改,则它可能位于错误的存储桶中,然后您将无法找到它,并且哈希表将在该密钥的任何 get 上错误地返回 null

    旁白:了解哈希表的内部工作原理有助于您了解优质hashcode 函数对您的键的重要性。因为糟糕的哈希码函数可能会导致桶中键的分布不均。由于桶只是列表,这会导致大量线性搜索,从而大大降低哈希表的有效性。例如这个可怕的哈希码函数将所有内容放在一个桶中,因此它实际上只是一个列表。

    public int hashcode { return 42; /*terrible hashcode example, don't use!*/ }
    

    这也是质数出现在好的哈希码函数中的原因之一,例如:

    public int hashcode {
        int hash = field1.hashcode();
        hash = hash*31 + field2.hashcode(); //note the prime 31
        hash = hash*31 + field3.hashcode();
        return hash;
    }
    

    【讨论】:

      【解决方案2】:

      总体思路是正确的,但细节不正确。

      HashTable 中的键不必是不可变的,它是调用 hashCode()(和 equals)方法的结果,需要保持不可变一致 (也就是说,哈希表的行为是可预测的)。

      从高级的角度来看,这是因为哈希表的工作方式:当插入 (key, value) 对时,key 的 hashCode 在内部用于计算出一个“桶”将放置值。而当valuekey 检索到时,hashCode 被再次计算,以找到桶。

      现在,如果在插入和检索之间的任何时间点,调用 hashCode 的结果发生变化,“查找桶”将与“插入”桶不同,事情将无法预测。

      总而言之,给定一个看起来像这样的 Key 对象(两个内部字符串组成对象,但在 hashCode / equals 中只考虑了一个 partOfHashCode):

      public static class Key {
        private String partOfHashCode;
        private String notPartOfHashCode;
      
        @Override
        public int hashCode() {
          final int prime = 31;
          int result = 1;
          result = prime * result + ((partOfHashCode == null) ? 0 : partOfHashCode.hashCode());
          return result;
        }
        @Override
        public boolean equals(Object obj) {
          if (this == obj)
            return true;
          if (obj == null)
            return false;
          if (getClass() != obj.getClass())
            return false;
          Key other = (Key) obj;
          if (partOfHashCode == null) {
            if (other.partOfHashCode != null)
              return false;
          } else if (!partOfHashCode.equals(other.partOfHashCode))
            return false;
          return true;
        }
      }
      

      这样用就好了:

      public static void main(String[] args) {
      
      Map<Key, String> myMap = new HashMap<>();
      Key key = new Key();
      key.partOfHashCode = "myHash";
      
      myMap.put(key, "value");
      
      key.notPartOfHashCode = "mutation of the key, but not of its hash/equals definition";
      
      System.out.println(myMap.get(key));
      }
      

      (这会在控制台中记录“值”对象)。

      但是这样用就不好了

      public static void main(String[] args) {
      
        Map<Key, String> myMap = new HashMap<>();
        Key key = new Key();
        key.partOfHashCode = "myHash";
      
        myMap.put(key, "value");
      
        key.partOfHashCode = "mutation of the hashCode of the key";
      
        System.out.println(myMap.get(key));
      }
      

      (最后一个示例可以在控制台中记录“null”)。

      有关此主题的更多信息,您还应该阅读 hashCode / equals 一致性。

      【讨论】:

        【解决方案3】:

        Java 没有内在的保证HashTable-keys 是不可变的。甚至不能保证他们的hashcode 保持不变。但是,如果您添加具有可变 hashCode 的键,您就有麻烦了。假设您要插入一个 hashCode 为 1 的键。然后将其插入对应于 1 的哈希桶中。然后将对象更改为具有 2 的 hashCode 并调用 hashMap.get(key)。当对象仍在hashTable 中时,系统会在对应于 2 的存储桶中查找,但不会在那里找到它。您甚至无法remove 该条目,因为它不会被发现。

        tl;dr 为了使您的应用程序正常工作HashTable-keys 需要具有不可变的hashcodes,但您必须自己处理这个事实。

        【讨论】:

          猜你喜欢
          • 2013-03-04
          • 1970-01-01
          • 1970-01-01
          • 2014-08-04
          • 1970-01-01
          • 2016-09-28
          • 2018-03-05
          • 2018-11-26
          • 1970-01-01
          相关资源
          最近更新 更多