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_LIST
和VALUE_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
中的每个元素,对于每个元素,我们使用相同的索引i
从VALUE_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()
方法生成一个从0
到KEY_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
循环同时遍历两个列表,使用每个Iterator
的next()
方法获取列表中的下一个元素。对于每一对元素,我们将键和值放入结果HashMap
,就像之前的例子一样。
7. 总结
在这篇文章中,我们通过实例学习了三种将给定列表组合成Map
的方法。
首先,我们使用for
循环和基于随机访问的Stream解决了问题。然后,我们讨论了当输入为LinkedList
时,随机访问方法的性能问题。
最后,我们看到了使用Iterator
的方法,这样无论我们使用哪种List
实现,都能获得更好的性能。
如往常一样,这里展示的所有代码片段都可以在GitHub上找到。