如何避免重复代码初始化 hashmap 的 hashmap?
Posted
技术标签:
【中文标题】如何避免重复代码初始化 hashmap 的 hashmap?【英文标题】:How can I avoid repeating code initializing a hashmap of hashmap? 【发布时间】:2020-06-30 22:51:58 【问题描述】:每个客户都有一个 id 和许多带有日期的发票,按 id 存储为客户的哈希图,按日期的发票哈希图:
HashMap<LocalDateTime, Invoice> allInvoices = allInvoicesAllClients.get(id);
if(allInvoices!=null)
allInvoices.put(date, invoice); //<---REPEATED CODE
else
allInvoices = new HashMap<>();
allInvoices.put(date, invoice); //<---REPEATED CODE
allInvoicesAllClients.put(id, allInvoices);
Java 解决方案似乎是使用getOrDefault
:
HashMap<LocalDateTime, Invoice> allInvoices = allInvoicesAllClients.getOrDefault(
id,
new HashMap<LocalDateTime, Invoice> () put(date, invoice);
);
但如果 get 不为 null,我仍然希望 put (date, invoice) 执行,并且仍然需要向“allInvoicesAllClients”添加数据。所以它似乎没有多大帮助。
【问题讨论】:
如果你不能保证键的唯一性,你最好的办法是让二级映射的值是 List这是Map#computeIfAbsent
的绝佳用例。你的 sn-p 基本上相当于:
allInvoicesAllClients.computeIfAbsent(id, key -> new HashMap<>()).put(date, invoice);
如果id
不是allInvoicesAllClients
中的键,那么它将创建从id
到新HashMap
的映射并返回新的HashMap
。如果id
作为键存在,那么它将返回现有的HashMap
。
【讨论】:
computeIfAbsent,做一个 get (id) (或一个 put 后跟一个 get (id) ),所以下一个 put 是为了更正项目 put(date),正确答案。allInvoicesAllClients.computeIfAbsent(id, key -> Map.of(date, invoice))
@Alexander-ReinstateMonica Map.of
创建一个不可修改的Map
,我不确定 OP 是否想要。
这段代码会不会比 OP 原来的效率低?问这个是因为我不熟悉 Java 如何处理 lambda 函数。【参考方案2】:
computeIfAbsent
是这种特殊情况的绝佳解决方案。总的来说,我想注意以下几点,因为还没有人提到它:
“外部”hashmap 只是存储了对“内部”hashmap 的引用,因此您只需重新排序操作即可避免代码重复:
HashMap<LocalDateTime, Invoice> allInvoices = allInvoicesAllClients.get(id);
if (allInvoices == null)
allInvoices = new HashMap<>();
allInvoicesAllClients.put(id, allInvoices);
allInvoices.put(date, invoice); // <--- no longer repeated
【讨论】:
在 Java 8 出现其奇特的computeIfAbsent()
方法之前几十年,我们就是这样做的!
我今天仍然在地图实现不提供单个 get-or-put-and-return-if-absent 方法的语言中使用这种方法。尽管这个问题是专门为 Java 8 标记的,但这仍然可能是其他语言的最佳解决方案。【参考方案3】:
您几乎不应该使用“双括号”地图初始化。
put(date, invoice);
在这种情况下,您应该使用computeIfAbsent
allInvoicesAllClients.computeIfAbsent(id, (k) -> new HashMap<>())
.put(date, allInvoices);
如果此 ID 没有映射,您将插入一个。结果将是现有的或计算的地图。然后,您可以put
该映射中的项目,并保证它不会为空。
【讨论】:
我不知道谁投了反对票,不是我,也许单行代码混淆了 allInvoicesAllClients,因为你使用 id 而不是 date,我会编辑它 @HernánEche 啊。我的错。谢谢。是的,id
的 put 也已完成。如果您愿意,您可以将computeIfAbsent
视为条件放置。它也返回值
"您几乎不应该使用“双括号”地图初始化。" 为什么? (我不怀疑你是对的;我是出于真正的好奇而问的。)
@Heinzi 因为它创建了一个匿名内部类。这包含对声明它的类的引用,如果您公开地图(例如通过 getter),它将防止封闭类被垃圾收集。此外,我发现它可能会让不太熟悉 Java 的人感到困惑。初始化程序块几乎从未使用过,这样写会使它看起来像
具有特殊含义,但它没有。
@Michael:有道理,谢谢。我完全忘记了匿名内部类总是非静态的(即使它们不需要)。【参考方案4】:
这比其他答案更长,但恕我直言更具可读性:
if(!allInvoicesAllClients.containsKey(id))
allInvoicesAllClients.put(id, new HashMap<LocalDateTime, Invoice>());
allInvoicesAllClients.get(id).put(date, invoice);
【讨论】:
这可能适用于 HashMap,但一般方法不是最佳的。如果这些是 ConcurrentHashMaps,那么这些操作就不是原子的。在这种情况下,check-then-act 将导致竞争条件。无论如何,反对者们都投了赞成票。【参考方案5】:您在这里做两件事:确保HashMap
存在,并向其中添加新条目。
现有代码确保在注册哈希映射之前首先插入新元素,但这不是必需的,因为HashMap
不关心这里的排序。这两种变体都不是线程安全的,因此您不会丢失任何东西。
所以,就像@Heinzi 建议的那样,您可以将这两个步骤分开。
我还要做的是将HashMap
的创建卸载到allInvoicesAllClients
对象,因此get
方法不能返回null
。
这也降低了单独线程之间竞争的可能性,这些线程既可以从get
获得null
指针,然后决定put
一个新的HashMap
与单个条目 - 第二个put
可能会丢弃第一个,丢失Invoice
对象。
【讨论】:
以上是关于如何避免重复代码初始化 hashmap 的 hashmap?的主要内容,如果未能解决你的问题,请参考以下文章