【译】怎样修改 HashMap 的 Key?

news2024/11/27 21:53:07

原文地址:https://www.baeldung.com/java-hashmap-modify-key

1. 概述

在 Java 中,HashMap 是一个广泛使用的数据结构,它以键值对的形式存储元素,提供快速的数据访问和检索。有时,在使用 HashMap 时,我们可能想要修改现有条目的键。
在本教程中,我们将探讨如何在 Java 的 HashMap 中修改一个键。

2. 使用 remove() 然后 put()

首先,让我们看看 HashMap 是如何存储键值对的。HashMap 内部使用 Node 类型来维护键值对:

static class Node<K,V> implements Map.Entry<K,V> {
 final int hash;
 final K key;
 V value;
 ...
}

如我们所见,键声明有 final 关键字。因此,我们不能在将其放入 HashMap 后重新分配一个键对象。

虽然我们不能简单地替换一个键,但我们仍然可以通过其他方式实现我们期望的结果。接下来,让我们从一个不同的角度来看待我们的问题。

假设我们在 HashMap 中有一个条目 K1 -> V 。现在,我们想要将 K1 更改为K2,以得到K2 -> V。实际上,实现这一点最直接的想法是找到 “K1” 的条目并用“K2”替换“K1”的键。然而,我们也可以删除 K1 -> V 的关联,并添加一个新的 K2 -> V的条目。

Map接口提供了 remove(key) 方法,可以通过其键从 map 中删除一个条目。此外,remove() 方法返回从 map 中删除的值。

接下来,让我们通过一个例子来看看这种方法是如何工作的。为了简单起见,我们将使用单元测试断言来验证结果是否符合我们的期望:

Map<String, Integer> playerMap = new HashMap<>();
playerMap.put("Kai", 42);
playerMap.put("Amanda", 88);
playerMap.put("Tom", 200);

上面的简单代码显示了一个 HashMap ,其中包含了几个玩家名(String)和他们的分数(Integer)。接下来,让我们将条目“Kai” -> 42中的玩家名“Kai”替换为“Eric”:

// 用Eric替换Kai
playerMap.put("Eric", playerMap.remove("Kai"));
assertFalse(playerMap.containsKey("Kai"));
assertTrue(playerMap.containsKey("Eric"));
assertEquals(42, playerMap.get("Eric"));

如我们所见,单行语句playerMap.put(“Eric”, playerMap.remove(“Kai”));做了两件事。它删除了键为“Kai”的条目,取出其值(42),并添加了一个新的条目“Eric” -> 42。

当我们运行测试时,它通过了。所以,这种方法如我们所期望的那样工作。

尽管我们的问题已经解决了,但还有一个潜在的问题。我们知道 HashMap 的键是一个 final 变量。所以,我们不能重新分配变量。但是我们可以修改一个 final对象的值。好吧,在我们的 playerMap 示例中,键是 String。我们不能改变它的值,因为字符串是不可变的。但是如果它是一个可变对象,我们可以通过修改键来解决问题吗?

接下来,让我们弄清楚。

3. 永不修改 HashMap 中的键

首先,我们不应该在 Java 的 HashMap 中使用一个可变对象作为键,因为这可能导致潜在的问题和意外的行为。

这是因为 HashMap 中的键对象用于计算一个哈希码,该哈希码决定了相应的值将被存储在哪个桶中。如果键是可变的并且在被用作 HashMap 中的键之后被更改,哈希码也可以更改。结果,我们将无法正确检索与键关联的值,因为它将位于错误的桶中。

接下来,让我们通过一个例子来理解它。

首先,我们创建一个只有一个属性的Player类:

class Player {
 private String name;
 public Player(String name) {
 this.name = name;
 }
 
 // 省略了getter和setter方法
 @Override
 public boolean equals(Object o) {
 if (this == o) {
 return true;
 }
 if (!(o instanceof Player)) {
 return false;
 }
 Player player = (Player) o;
 return name.equals(player.name);
 }
 
 @Override
 public int hashCode() {
 return name.hashCode();
 }
}

如我们所见,Player 类在 name 属性上有一个 setter ,所以它是可变的。此外,hashCode() 方法使用 name 属性来计算哈希码。这意味着更改 Player 对象的名字可以使它具有不同的哈希码。

接下来,让我们创建一个 map,并在其中放入一些条目,使用 Player对象作为键:

Map<Player, Integer> myMap = new HashMap<>();
Player kai = new Player("Kai");
Player tom = new Player("Tom");
Player amanda = new Player("Amanda");
myMap.put(kai, 42);
myMap.put(amanda, 88);
myMap.put(tom, 200);
assertTrue(myMap.containsKey(kai));

接下来,让我们将玩家 kai 的名字从 “Kai” 更改为 “Eric”,然后验证我们是否可以得到预期的结果:

// 将Kai的名字更改为Eric
kai.setName("Eric");
assertEquals("Eric", kai.getName());
Player eric = new Player("Eric");
assertEquals(eric, kai);
// 现在,map中既不包含Kai也不包含Eric:
assertFalse(myMap.containsKey(kai));
assertFalse(myMap.containsKey(eric));

如上面的测试所示,更改 kai 的名字为 “Eric” 后,我们无法再使用 kai 或 eric 检索 “Eric” -> 42 的 Entry。然而,对象 Player(“Eric”) 存在于 map 中作为一个键:

// 虽然Player("Eric")存在:
long ericCount = myMap.keySet()
 .stream()
 .filter(player -> player.getName()
 .equals("Eric"))
 .count();
assertEquals(1, ericCount);

要理解为什么会这样,我们首先需要了解 HashMap 是如何工作的。

HashMap 维护一个内部哈希表来存储添加到 map 中的键的哈希码。一个哈希码引用一个 map 条目。当我们检索一个条目时,例如通过使用 get(key)方法,HashMap 计算给定键对象的哈希码,并在哈希表中查找哈希码。

在上面的例子中,我们将 kai(“Kai”) 放入 map 中。所以,哈希码是基于字符串“Kai”计算的。HashMap 存储了结果,让我们说 “hash-kai”,在哈希表中。后来,我们将 kai(“Kai”) 更改为 kai(“Eric”)。当我们试图通过 kai(“Eric”) 检索条目时,HashMap计算“hash-eric”作为哈希码。然后,它在哈希表中查找它。当然,它找不到它。

不难想象,如果我们在一个真正的应用程序中这样做,这种意外行为的根本原因将很难找到。

因此,我们不应该在 HashMap 中使用可变对象作为键。更进一步,我们永远不应该修改键。

4. 结论

在本文中,我们学习了remove() 然后 put()方法来替换 HashMap 中的一个键。此外,我们通过一个例子讨论了为什么我们应该避免在 HashMap 中使用可变对象作为键,以及为什么我们永远不应该修改 HashMap 中的键。

一如既往,示例的完整源代码可以在 GitHub 上找到。

译者注

想要深入理解这个问题需要阅读 HashMap 的 put 和 containsKey 的源码。

这里作图并不严谨,只是帮助理解问题:

Kai、Tom、Amanda 分别 put 之后,效果大致如下:
在这里插入图片描述

其中 Eric 未真实 put,紫色这里只是模拟如果 put 之后,一个虚拟的位置(即 如果有 Eric 这个 Key put 之后应该在哪里)。

因为 Player 是可变的,那么直接 name 设置为 Eric 会导致 Map 中原本为 Kai 的对象的 name 改为了 Eric (注意此时该对象位置未发生变化)。
在这里插入图片描述

// 现在,map中既不包含Kai也不包含Eric:
assertFalse(myMap.containsKey(kai));
assertFalse(myMap.containsKey(eric));

myMap.containsKey(kai) 通过 containsKey 去查找时,此时 kai 的 name 已经是 Eric ,会根据 hashCode 计算到紫色 Eric 这个位置取对象,发现没有元素。

myMap.containsKey(eric) 通过 containsKey 去查找时,eric 的 name 是 Eric ,也会根据 hashCode 计算到紫色 Eric 这个位置取对象,发现没有元素。

// 虽然Player("Eric")存在:
long ericCount = myMap.keySet()
 .stream()
 .filter(player -> player.getName()
 .equals("Eric"))
 .count();
assertEquals(1, ericCount);

则是遍历所有 Entry 时,对象的 name 里面有 Eric (即对象 Kai 的值)。

如果最初存入 Eric, 因为重写了 hashCode 和 equals 方法,两个 eric 等价,所以 containsKey 会是 true。

 Map<Player, Integer> myMap = new HashMap<>();
 Player kai = new Player("Kai");
 Player tom = new Player("Tom");
 Player amanda = new Player("Amanda");
 Player eric = new Player("Eric");
 myMap.put(kai, 42);
 myMap.put(amanda, 88);
 myMap.put(tom, 200);
 // 先存进去
 myMap.put(eric, 300);
 assertTrue(myMap.containsKey(kai));
// new 一个 Eric
 Player ericNew = new Player("Eric");
 //依然存在
 assertTrue(myMap.containsKey(ericNew));

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1008665.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【算法题】1222. 可以攻击国王的皇后

题目&#xff1a; 在一个 8x8 的棋盘上&#xff0c;放置着若干「黑皇后」和一个「白国王」。 给定一个由整数坐标组成的数组 queens &#xff0c;表示黑皇后的位置&#xff1b;以及一对坐标 king &#xff0c;表示白国王的位置&#xff0c;返回所有可以攻击国王的皇后的坐标(…

重启人生,重新出发

Hello, World! 我是EarlGrey&#xff0c;这是时隔两年多后在公众号第一次发文。也许有些老朋友已经忘记我了&#xff0c;也有少数朋友是最近才开始关注的&#xff0c;所以谈正题之前&#xff0c;我先花点时间介绍一下自己。 我是谁 我是英语专业出身&#xff0c;从北外高翻毕业…

使用 SimPowerSystems 的混合技术风电场的无功功率管理(Simulink)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

什么是 JxBrowser

什么是 JxBrowser 文章目录 什么是 JxBrowser如何使用 JxBrowser容易集成支持的平台Java丰富的文档如何运行主要功能值得信赖成熟的专业技术团队及时的支持与帮助参考资料 JxBrowser 是一个商业跨平台 Java 库&#xff0c;可以让您将基于 Chromium 的网页浏览器控件集成到您的 …

【ES实战】ES中关于segment的小结

文章目录 ES中关于segment的小结ES中segment相关的原理在Lucene中的产生segment的过程。&#xff08;Lucene commit过程&#xff09;ES为了实现近实时可查询做了哪些缩短数据可被搜索的等待时长增加数据的可靠性优化segment的数量 段合并自动合并强制合并 相关配置translog合并…

Python 学习之路 03 之循环

&#x1f600;前言 欢迎来到 Python 循环和流程控制的基础教程&#xff01;无论您是一名新手&#xff0c;还是希望复习 Python 编程的基本知识&#xff0c;这个教程都是一个非常好的资源。在这份教程中&#xff0c;我们将探索 Python 中的不同循环结构和流程控制机制&#xff0…

火山引擎DataWind产品可视化能力揭秘

引言 BI是商业智能(Business Intelligence)的缩写&#xff0c;是一种将企业中现有的数据进行有效的整合的平台&#xff0c;它可以帮助企业、组织和个人更好地了解其业务状况、发现问题&#xff0c;并进行决策。BI产品普遍采用可视化的方式&#xff0c;可以帮助用户更直观、更高…

快速傅里叶变换

引言 目标 傅里叶变化&#xff08;Fourier transform&#xff09;是一种信号处理技术&#xff0c;它可以将时间信号转换为频率信号&#xff0c;即将一组具有相同数量频率的正弦波叠加在一起&#xff0c;形成一组新的正弦波。如果我们把时间信号从频域转换到时域&#xff0c;那么…

Drupal __ 8.5.0 __ XSS文件上传 __CVE-2019-6341

Drupal __ 8.5.0 __ XSS文件上传 __CVE-2019-6341 说明内容漏洞编号CVE-2019-6341漏洞名称Drupal XSS漏洞漏洞评级中危影响范围在7.65之前的Drupal 7版本中&#xff1b; 8.6.13之前的Drupal 8.6版本; 8.5.14之前的Drupal 8.5版本。漏洞描述Drupal诞生于2000年&#xff0c;是一…

PbootCMS在搭建网站

1、打开网站 https://www.pbootcms.com/ 2、点击 “本站” 下载最新的网站代码 3、在本地laragon/www下创建目录&#xff08;hejuwuye&#xff09;&#xff0c;并将代码放进去 4、创建本地数据库&#xff0c;数据库名称为&#xff1a; hejuwuye&#xff0c;然后将static/bac…

25.Xaml DateGrid控件---->默认单选,可以多项选择的网格控件

1.运行效果 2.运行源码 a.Xaml源码 <Window x:Class="testView.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.mic…

深入理解JVM虚拟机第五篇:一些常用的JVM虚拟机(二)

文章目录 一&#xff1a;JRockit VM的介绍 二&#xff1a;J9 VM的介绍 三&#xff1a;KVM和CDC/CLDC Hotspot 四&#xff1a;Azul VM的介绍 五&#xff1a;Liquid VM的介绍 六&#xff1a;Apache Harmoney 七&#xff1a;Microsoft JVM 八&#xff1a;Taobao JVM 九&a…

第三方软件测试机构有哪些测试服务软件测试报告收费标准是怎样的?

软件验收机构 一、什么是第三方软件测试机构? 第三方软件测试机构是区别于软件开发公司以及软件需求方的第三方机构&#xff0c;软件企业将软件测试外包给第三方软件测试机构已经成为了行业发展趋势。既省心省力&#xff0c;又降低企业成本&#xff0c;得出的软件测试结果以…

【Proteus仿真】【STM32单片机】四驱寻迹避障小车

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 系统运行后&#xff0c;LCD1602显示红外、超声波检测状态和距离、小车运行状态。可通过K1键可手动切换模式&#xff0c;寻迹、避障、蓝牙遥控&#xff1b;也可通过蓝牙发送指令切换模式&#xff1b; 当处…

绝佳用户体验:构建响应式网页设计的关键原则

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 当谈到前端开发时&#…

使用Java创建一个简单的图书管理系统

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 目录 图书管理系统项目简…

for...in...与for..of...

for...in... 仅迭代自身的属性for...in 语句以任意顺序迭代一个对象的除Symbol以外的可枚举属性&#xff0c;包括继承的可枚举属性。 for ... in是为遍历对象属性而构建的&#xff0c;不建议与数组一起使用&#xff0c;数组可以Array.prototype.forEach()和for ... of const …

Python实现猎人猎物优化算法(HPO)优化Catboost回归模型(CatBoostRegressor算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 猎人猎物优化搜索算法(Hunter–prey optimizer, HPO)是由Naruei& Keynia于2022年提出的一种最新的…

新手如何开始Microstation CE版二次开发

一步步学习MicroStation CE MDL&#xff08;C&#xff09;开发 - 技术资料库 - Bentley 中国优先社区 - Bentley Communities https://communities.bentley.com/communities/other_communities/chinafirst/w/chinawiki/57704/microstation-ce-mdl-c一步步学习MicroStation CE A…

python项目制作docker镜像,加装引用模块,部署运行!

一、创建Dockerfile # 基于python:3.10.4版本创建容器 FROM python:3.10.4 # 在容器中创建工作目录 RUN mkdir /app # 将当前Dockerfile目录下的所有文件夹和文件拷贝到容器/app目录下 COPY . /app# 由于python程序用到了requests模块和yaml模块&#xff0c; # python:3.10.4基…