Java中如何向HashMap内的ArrayList添加元素实现字符索引映射
需求为遍历存储字符的ArrayList,构建HashMap:键为列表中的字符,值为该字符在列表中所有出现位置的索引组成的ArrayList。
示例输入数组:[X,X,Y,Z,X],预期返回映射结构:
{ X: [0,1,4], Y: [2], Z: [3] }
原有代码运行报错的核心原因:ArrayList.add()方法返回值为boolean类型,但computeIfPresent方法传入的lambda表达式要求返回更新后的值对象(即List类型),类型不匹配。另外原有代码中声明的arrayOfIndexes变量未被任何逻辑使用,可直接删除。原有错误代码如下:
/* turn the pattern into a List of characters turn the List into a HashMap with the keys as the characters and the values as the indexes of the characters in the List */ ArrayList<Character> listOfPatternChars = convertStringToCharList(userPattern); HashMap<Character,ArrayList<Integer>> mapOfPatternCharsIndex = new HashMap<Character,ArrayList<Integer>>(); ArrayList<ArrayList<Integer>> arrayOfIndexes = new ArrayList<ArrayList<Integer>>(); for (int i = 0; i < listOfPatternChars.size(); i++) { mapOfPatternCharsIndex.putIfAbsent(listOfPatternChars.get(i), new ArrayList<Integer>()); mapOfPatternCharsIndex.computeIfPresent(listOfPatternChars.get(i), (k,v) -> v.add(i) ); }
疑问点:JavaScript中可通过展开运算符实现(k, v) => [...v,i]的更新逻辑,Java中是否存在类似实现。
Java没有和JavaScript展开运算符完全等价的语法糖,针对该场景可按以下方式实现:
- 修复原有
computeIfPresent写法:ArrayList是可变对象,add操作会直接修改原列表内容,只需要在lambda中执行add后返回原列表对象即可,修正后循环逻辑:
for (int i = 0; i < listOfPatternChars.size(); i++) { Character currentChar = listOfPatternChars.get(i); mapOfPatternCharsIndex.putIfAbsent(currentChar, new ArrayList<>()); mapOfPatternCharsIndex.computeIfPresent(currentChar, (k, v) -> { v.add(i); return v; }); }
代码中使用Java 7+支持的菱形语法简化泛型声明,同时用临时变量存储当前遍历的字符,避免重复调用
get(i)取值,减少冗余计算。
- 更简洁的标准写法:直接用
computeIfAbsent合并初始化和取值逻辑,无需分开写putIfAbsent和computeIfPresent,代码更短且逻辑更清晰:
for (int i = 0; i < listOfPatternChars.size(); i++) { mapOfPatternCharsIndex.computeIfAbsent(listOfPatternChars.get(i), key -> new ArrayList<>()) .add(i); }
该写法逻辑为:computeIfAbsent会先判断键是否存在,若不存在则通过lambda生成新的ArrayList作为对应值存入Map,最终返回该键关联的值(无论值是原有还是新生成的),拿到值后直接调用add(i)存入当前索引即可,完全规避返回值类型不匹配的问题,是这类分组收集索引场景的通用实现。
如果要实现类似JS展开运算符那种「不修改原列表,基于原列表生成追加元素后的新列表」的不可变逻辑,Java 8+可通过Stream API实现:
(k, v) -> Stream.concat(v.stream(), Stream.of(i)).collect(Collectors.toList())
但该方式每次追加元素都会生成全新的列表,性能远低于直接修改可变列表的方案,仅在需要保证原列表不可变的特殊场景下使用,当前场景不推荐。
内容的提问来源于stack exchange,提问作者João Pedro

