自定义对象作为HashMap的key注意事项

后端 潘老师 7个月前 (10-13) 145 ℃ (0) 扫码查看

在Java中,我们可以使用对象作为HashMap的key,但在这样做时需要考虑重要的因素和准则。这的确是一个常见的面试问题,需要对HashMap的工作原理有很好的理解。以下是用户定义的类用作HashMap中的键时,需要考虑的几个重要因素:

1. 键必须遵守hashCode()和equals()之间的契约

为了设计一个良好的键,基本需求是“我们应该能够在不失败的情况下从map中检索值对象”,否则无论你构建多么复杂的数据结构,都将毫无用处。

为了决定我们是否已经创建了一个良好的键,我们必须了解“HashMap的工作原理”。“HashMap的工作原理”部分请自行阅读相关文章,但总结来说,它是基于哈希的原则工作的。

在HashMap中,键的hashCode()主要与equals()方法一起使用,用于将键放入map中,然后从map中获取它。因此,我们的唯一关注点是这两个方法。

  • equals() – 验证两个对象的相等性,这在我们的案例中是键。要重写以提供比较两个键的逻辑。
  • hashCode() – 在运行时返回键的唯一整数值。这个值用于查找map中的桶位置。

通常,如果重写equals()方法,则必须重写hashCode()方法,以保持hashCode()方法的一般合同,该合同规定相等的对象必须具有相等的哈希码。

2. 如果允许更改键的hashCode会发生什么?

如上所述,哈希码有助于计算存储键值对的map中的桶位置。不同的哈希码值可能指的是不同的桶位置。

如果不小心,在将键值对放入map后,更改了键对象的哈希码,那么几乎不可能从map中获取值对象,因为我们不知道在哪个桶中放入了键值对。旧的键值对是不可访问的,因此这是一种内存泄漏情况。

在运行时,JVM为每个对象计算哈希码并按需提供。当我们修改对象的状态时,JVM设置一个标志,表示对象已被修改,并且必须再次计算哈希码。所以,下一次调用对象的hashCode()方法时,JVM会重新计算该对象的哈希码。

3. 我们应该使HashMap的键不可变

对于上述基本推理,建议键对象是不可变的。不可变性确保我们每次都会得到相同的哈希码,用于键对象。因此,它实际上一次性解决了几乎所有问题。但是,再次强调,这种类必须遵守hashCode()和equals()方法的合同。

这也是为什么像String、Integer或其他包装类这样的不可变类是良好的键对象候选项的主要原因。这也回答了为什么字符串是Java中流行的HashMap键的问题。

但请记住,不可变性是建议而不是强制性的。如果你想将可变对象用作HashMap中的键,那么你必须确保键对象的状态更改不会更改对象的哈希码。这可以通过重写hashCode()方法来实现。但是,你必须确保你遵守equals()的合同。

4. 自定义键的HashMap示例

在下面的示例中,我创建了一个Account类,仅包含两个字段以简化。我已经重写了hashCode和equals方法,以便它只使用帐户号来验证Account对象的唯一性。帐户类的所有其他可能属性都可以在运行时更改。

public class Account
{
    private int accountNumber;
    private String holderName;
    public Account(int accountNumber) {
        this.accountNumber = accountNumber;
    }
    public String getHolderName() {
        return holderName;
    }
    public void setHolderName(String holderName) {
        this.holderName = holderName;
    }
    public int getAccountNumber() {
        return accountNumber;
    }
    //Depends only on account number
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + accountNumber;
        return result;
    }
    //仅比较 account numbers
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Account other = (Account) obj;
        if (accountNumber != other.accountNumber)
            return false;
        return true;
    }
}

这是否会导致不良行为?

不会。原因是Account类的实现遵守了这样的合同:“只要它们相等,相等的对象必须产生相同的哈希码,但不相等的对象不需要产生不同的哈希码。”

5. 示例

让我们测试一下上述分析中的Account类。

//创建具有可变键的HashMap
HashMap<Account, String> map = new HashMap<Account, String>();
//创建 key 1
Account a1 = new Account(1);
a1.setHolderName("A_ONE");
//创建 key 2
Account a2 = new Account(2);
a2.setHolderName("A_TWO");
//在map中存入可变键和值
map.put(a1, a1.getHolderName());
map.put(a2, a2.getHolderName());
//更改key状态,哈希码便重新计算
a1.setHolderName("Defaulter");
a2.setHolderName("Bankrupt");
//成功!我们能够获取value
System.out.println(map.get(a1)); //打印 A_ONE
System.out.println(map.get(a2)); //打印 A_TWO
//尝试使用新创建的具有相同account number的key
Account a3 = new Account(1);
a3.setHolderName("A_THREE");
//成功!我们仍然能够取回账号1的value
System.out.println(map.get(a3)); //打印  A_ONE

6. 结论

在本教程中,我们学习了如何设计一个类,可以用作Map实例中存储键值对的键。

作为最佳实践:

  • 键类应该是不可变的。
  • 在大多数情况下,默认的hashCode()和equals()方法已经足够好,但如果重写了一个方法,那么应该重写另一个方法,以确保它们之间遵守合同。

版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/9526.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】