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

IT 文章2年前 (2023)发布 小编
0 0 0

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

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

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

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

ad

程序员导航

优网导航旗下整合全网优质开发资源,一站式IT编程学习与工具大全网站

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

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

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

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

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

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

ad

AI 工具导航

优网导航旗下AI工具导航,精选全球千款优质 AI 工具集

在运行时,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;
	}
}

这是否会导致不良行为?

ad

免费在线工具导航

优网导航旗下整合全网优质免费、免注册的在线工具导航大全

不会。原因是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()方法已经足够好,但如果重写了一个方法,那么应该重写另一个方法,以确保它们之间遵守合同。
© 版权声明

相关文章

暂无评论

暂无评论...