1. 概述

在Java编程中,我们经常需要将两个独立的列表关联起来。换句话说,给定一个键列表和一个值列表,我们希望得到一个映射(Map),其中每个键列表的元素都与值列表中对应的元素关联。

在这篇教程中,我们将探讨如何以不同的方式实现这一目标。

2. 问题介绍

通常,通过一个例子来理解这个问题。假设我们有以下两个列表:

final List<String> KEY_LIST = Arrays.asList("Number One", "Number Two", "Number Three", "Number Four", "Number Five");
final List<Integer> VALUE_LIST = Arrays.asList(1, 2, 3, 4, 5);

现在,我们想要将上述两个列表与一个Map关联起来。首先,让我们初始化一个包含预期键值对的HashMap

final Map<String, Integer> EXPECTED_MAP = new HashMap<String, Integer>() {{
    put("Number One", 1);
    put("Number Two", 2);
    put("Number Three", 3);
    put("Number Four", 4);
    put("Number Five", 5);
}};

如上代码所示,合并两个列表的规则相当直接。接下来,我们将看到如何实现这一点。

3. 验证注意事项

了解问题后,我们可能会意识到给定的两个列表必须具有相同的元素数量,例如KEY_LISTVALUE_LIST。然而,在实际操作中,由于无法预测数据质量,两个给定的列表可能具有不同的大小。在这种情况下,我们必须遵循要求进行进一步的操作。通常,可能有两种选择:

  • 抛出异常并中断关联操作
  • 将不匹配问题报告为警告,然后继续创建仅包含匹配元素的Map对象

我们可以使用简单的if检查来实现这一点:

int size = KEY_LIST.size();
if (KEY_LIST.size() != VALUE_LIST.size()) {
    // throw an exception or print a warning and take the smaller size and continue:
    size = min(KEY_LIST.size(), VALUE_LIST.size());
}

// using the size variable for further processings

为了简化,我们假设两个列表始终具有相同的大小,并且在后续的代码示例中省略了此验证。此外,我们将使用单元测试断言来验证方法是否返回预期结果。

4. 循环填充Map

由于两个输入列表具有相同的大小,我们可以通过一个循环将它们与Map关联起来。现在来看看具体做法:

Map<String, Integer> result = new HashMap<>();

for (int i = 0; i < KEY_LIST.size(); i++) {
    result.put(KEY_LIST.get(i), VALUE_LIST.get(i));
}
assertEquals(EXPECTED_MAP, result);

如上例所示,我们创建了一个名为result的新HashMap。然后,我们使用for循环遍历KEY_LIST中的每个元素,对于每个元素,我们使用相同的索引iVALUE_LIST中获取相应的元素。接着,put()方法将键值对填入result映射中。

5. 使用Stream API

Stream API提供了许多简洁而高效的处理Java集合的方法。接下来,我们使用Java Stream API来关联两个列表:

Map<String, Integer> result = IntStream.range(0, KEY_LIST.size())
  .boxed()
  .collect(Collectors.toMap(KEY_LIST::get, VALUE_LIST::get));
assertEquals(EXPECTED_MAP, result);

如代码所示,IntStream.range()方法生成一个从0KEY_LIST大小的整数流。值得一提的是,IntStream基本流。因此,我们使用boxed()方法将IntStream转换为Stream<Integer>,以便使用collect()方法将元素收集到Map中。

6. 使用Iterator

我们已经学习了两种方法将两个列表关联成Map。但是,如果我们仔细观察这两种解决方案,会发现两者都使用了List.get()方法来通过索引访问元素,以此建立关联。这称为随机访问。

如果我们的列表是ArrayList(最常见的情况),数据由数组支持,因此随机访问速度很快

然而,如果我们得到的是两个大型LinkedList通过索引访问元素可能会很慢。这是因为LinkedList需要从列表的开头遍历到所需的索引。

因此,使用Iterator可以更有效地遍历列表,特别是对于大型列表:

Map<String, Integer> result = new HashMap<>();

Iterator<String> ik = KEY_LIST.iterator();
Iterator<Integer> iv = VALUE_LIST.iterator();
while (ik.hasNext() && iv.hasNext()) {
    result.put(ik.next(), iv.next());
}

assertEquals(EXPECTED_MAP, result);

在这个例子中,我们为每个列表创建一个Iterator对象。然后,我们使用一个while循环同时遍历两个列表,使用每个Iteratornext()方法获取列表中的下一个元素。对于每一对元素,我们将键和值放入结果HashMap,就像之前的例子一样。

7. 总结

在这篇文章中,我们通过实例学习了三种将给定列表组合成Map的方法。

首先,我们使用for循环和基于随机访问的Stream解决了问题。然后,我们讨论了当输入为LinkedList时,随机访问方法的性能问题。

最后,我们看到了使用Iterator的方法,这样无论我们使用哪种List实现,都能获得更好的性能。

如往常一样,这里展示的所有代码片段都可以在GitHub上找到。