1. 概述

在Java中操作数据结构时,一个常见的场景是将HashMap中的值和键提取出来,并组织成一个ArrayList。本教程将探讨实现这一目标的各种实用方法。

2. 问题介绍

首先,我们创建一个HashMap对象作为示例:

static final HashMap<String, String> DEV_MAP;
static {
    DEV_MAP = new HashMap<>();
    DEV_MAP.put("Kent", "Linux");
    DEV_MAP.put("Eric", "MacOS");
    DEV_MAP.put("Kevin", "Windows");
    DEV_MAP.put("Michal", "MacOS");
    DEV_MAP.put("Saajan", "Linux");
}

如上代码所示,我们使用静态块初始化了一个HashMap。该映射包含一些开发者及其主要使用的操作系统。

当我们讨论从映射中提取键值对列表时,会根据特定需求出现不同的情况。

一种情况是,给定索引i,原始映射中元素keyList[i]valueList[i]之间存在直接关联。简单来说,在原始映射的上下文中,对应索引的列表元素之间存在相关性:

Map:
    k1 -> v1
    k2 -> v2
    k3 -> v3

index    :  0,  1,  2
KeyList  : k1, k2, k3
ValueList: v1, v2, v3

第二种情况相对简单,目标是从提供的映射中提取键列表和值列表,而不考虑原始键值对的关联。

本教程将涵盖这两种情况。为了确保清晰性和验证,我们将使用单元测试断言来验证每种方法结果的正确性。

3. 使用HashMap的keySet()values()方法

首先,让我们解决更简单的情况:忽略元素之间的关联,从DEV_MAP中获取键和值列表。

Map接口提供了两个方法,可以帮助我们快速解决这个问题:

  • keySet() - 获取映射中所有键作为Set
  • values() - 返回所有值作为Collection

我们可以将SetCollection传递给ArrayList的构造函数,以获得预期的列表对象,例如获取键列表:

List<String> keyList = new ArrayList<>(DEV_MAP.keySet());
assertEquals(Lists.newArrayList("Kent", "Eric", "Kevin", "Michal", "Saajan"), keyList);

然而,运行测试可能会失败,因为HashMap不维护其条目的顺序。换句话说,列表中元素的顺序无法预测。

接下来,我们可以使用AssertJcontainsExactlyInAnyOrder()方法来检查元素并忽略它们的顺序:

assertThat(keyList).containsExactlyInAnyOrder("Kent", "Eric", "Kevin", "Michal", "Saajan");

同样,我们也可以通过Map.values()获取值列表:

List<String> valueList = new ArrayList<>(DEV_MAP.values());
assertThat(valueList).containsExactlyInAnyOrder("Linux", "MacOS", "Windows", "MacOS", "Linux");

4. 获取关联的键值列表

现在,我们转向另一种情况:获取一个键列表和一个值列表,其中keyList[i]valueList[i]在映射中保持关联。

首先,我们创建一个方法来验证这两个列表是否符合预期:

void assertKeyAndValueList(List<String> keyList, List<String> valueList) {
    assertThat(keyList).containsExactlyInAnyOrder("Kent", "Eric", "Kevin", "Michal", "Saajan");
    assertThat(valueList).containsExactlyInAnyOrder("Linux", "MacOS", "Windows", "MacOS", "Linux");
    for (int i = 0; i < keyList.size(); i++) {
        assertThat(DEV_MAP).containsEntry(keyList.get(i), valueList.get(i));
    }
}

如上述方法所示,除了验证两个列表应包含所需的元素外,我们还确保它们在相同索引处的对应元素在DEV_MAP中有关联。

解决这个问题的一种方法是遍历映射的条目,将每个条目的键和值填充到预先初始化的两个列表中:

List<String> keyList = new ArrayList<>();
List<String> valueList = new ArrayList<>();
for (Map.Entry<String, String> entry : DEV_MAP.entrySet()) {
    keyList.add(entry.getKey());
    valueList.add(entry.getValue());
}

assertKeyAndValueList(keyList, valueList);

如果运行测试,它会通过。所以这种方法可以完成任务。

如果我们使用Java 8或更高版本,可以使用forEach()调用和lambda表达式来替换for循环,提高代码可读性:

List<String> keyList = new ArrayList<>();
List<String> valueList = new ArrayList<>();
DEV_MAP.forEach((k, v) -> {
    keyList.add(k);
    valueList.add(v);
});

assertKeyAndValueList(keyList, valueList);

5. 总结

在这篇文章中,我们首先讨论了从HashMap获取键列表和值列表的两种情况。随后,我们探讨了如何在每种情况下解决问题。

如往常一样,这些示例的完整源代码可在GitHub上找到:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-collections-maps-3