Java 转换List至Map
转换List至Map是很常见的任务。文本我们提供几种方式实现。假设List中的每个元素有一个唯一标识作为Map中的key。
示例数据结构
首先,我们定义模型数据:
@Data
public class Animal {
private int id;
private String name;
}
id字段值是唯一的,因此可以作为key。首先使用传统方法进行转换.
## java 8 之前方式
很显然,我们可以使用核心java API实现:
public Map<Integer, Animal> convertListBeforeJava8(List<Animal> list) {
Map<Integer, Animal> map = new HashMap<>();
for (Animal animal : list) {
map.put(animal.getId(), animal);
}
return map;
}
测试代码:
@Test
public void whenConvertBeforeJava8_thenReturnMapWithTheSameElements() {
Map<Integer, Animal> map = convertListService.convertListBeforeJava8(list);
assertThat(map.values(), containsInAnyOrder(list.toArray()));
}
使用java 8 方式
Java 8 可以通过stream 和 Collectors实现:
public Map<Integer, Animal> convertListAfterJava8(List<Animal> list) {
Map<Integer, Animal> map = list.stream()
.collect(Collectors.toMap(Animal::getId, animal -> animal));
return map;
}
再次进行测试:
@Test
public void whenConvertAfterJava8_thenReturnMapWithTheSameElements() {
Map<Integer, Animal> map = convertListService.convertListAfterJava8(list);
assertThat(
map.values(),
containsInAnyOrder(list.toArray()));
}
使用Guava 库
除了java 提供的API,我们也可以使用第三方库实现转换,Guava 提供了Maps.uniqueIndex() 方法可以实现:
public Map<Integer, Animal> convertListWithGuava(List<Animal> list) {
Map<Integer, Animal> map = Maps
.uniqueIndex(list, Animal::getId);
return map;
}
对应测试代码:
@Test
public void whenConvertWithGuava_thenReturnMapWithTheSameElements() {
Map<Integer, Animal> map = convertListService
.convertListWithGuava(list);
assertThat(
map.values(),
containsInAnyOrder(list.toArray()));
}
使用Apache Commons库
Apache Commons库提供了MapUtils.populateMap():
public Map<Integer, Animal> convertListWithApacheCommons2(List<Animal> list) {
Map<Integer, Animal> map = new HashMap<>();
MapUtils.populateMap(map, list, Animal::getId);
return map;
}
测试代码:
@Test
public void whenConvertWithApacheCommons2_thenReturnMapWithTheSameElements() {
Map<Integer, Animal> map = convertListService
.convertListWithApacheCommons2(list);
assertThat(
map.values(),
containsInAnyOrder(list.toArray()));
}
键冲突
前面我们假设键对应值是唯一的,如果不唯一会出现什么情况。
首先,我们准备测试数据,id值有重复:
@Before
public void init() {
this.duplicatedIdList = new ArrayList<>();
Animal cat = new Animal(1, "Cat");
duplicatedIdList.add(cat);
Animal dog = new Animal(2, "Dog");
duplicatedIdList.add(dog);
Animal pig = new Animal(3, "Pig");
duplicatedIdList.add(pig);
Animal cow = new Animal(4, "Cow");
duplicatedIdList.add(cow);
Animal goat= new Animal(4, "Goat");
duplicatedIdList.add(goat);
}
cow 和 goat的id都是4。
值重复情况测试对比
java Map的put方法实现为:键相同时后面的值会覆盖前面的值。因此,使用java 8 之前实现和Apache Commons MapUtils.populateMap() 方式结果一致:
@Test
public void whenConvertBeforeJava8_thenReturnMapWithRewrittenElement() {
Map<Integer, Animal> map = convertListService
.convertListBeforeJava8(duplicatedIdList);
assertThat(map.values(), hasSize(4));
assertThat(map.values(), hasItem(duplicatedIdList.get(4)));
}
@Test
public void whenConvertWithApacheCommons_thenReturnMapWithRewrittenElement() {
Map<Integer, Animal> map = convertListService
.convertListWithApacheCommons(duplicatedIdList);
assertThat(map.values(), hasSize(4));
assertThat(map.values(), hasItem(duplicatedIdList.get(4)));
}
我们看到,goat覆盖了cow的id。
相反,Collectors.toMap() 和 MapUtils.populateMap() 方法分别抛IllegalStateException和IllegalArgumentException异常:
@Test(expected = IllegalStateException.class)
public void givenADupIdList_whenConvertAfterJava8_thenException() {
convertListService.convertListAfterJava8(duplicatedIdList);
}
@Test(expected = IllegalArgumentException.class)
public void givenADupIdList_whenConvertWithGuava_thenException() {
convertListService.convertListWithGuava(duplicatedIdList);
}
解决办法
Collectors.toMap提供了重载方法,第三个参数提供meargeFunction,我们可以这样实现:
Map<Integer, Animal> map = list.stream()
.collect(Collectors.toMap(Animal::getId, animal -> animal),(oldValue, newValue) -> newvalue);
return map;
当然你也可以写(oldValue, newValue) -> oldValue,保留原来的值,取决你的需要。
## 总结
本文我们探讨了几种方式实现list转换为map,同时说明了key值重复情况及解决办法。