使用Stream实现三数之和遇ConcurrentModificationException问题求助
问题描述
给定数组[6,4,7,9,3,12],需找出其中和为13的三个数,要求仅使用Java Stream实现,不能使用常规循环。尝试用map和flatMap实现时,因用到void方法不得不加入forEach循环,当前执行savedNumbers.add(fixMeCurrentNumber)时抛出ConcurrentModificationException,试过传递嵌套Wrapper对象无效,不想使用Iterator,求异常修复方案或更简洁的Stream实现思路。
原代码实现
DemoApplication.java
package com.example.demo_david; import models.Wrapper; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); Integer value = 13; List<Integer> fixMe = new ArrayList<>(List.of(6,4,7,9,3,12)); System.out.println("answer: " + findTheOnesThatEqualTheResult(value, fixMe)); } static List<Integer> findTheOnesThatEqualTheResult(int maxValue, List<Integer> list){ Wrapper wrapper = new Wrapper(); List<ArrayList<Integer>> collectedList = list.stream().map(fixMeCurrentNumber -> computeLogic(maxValue, wrapper, fixMeCurrentNumber)).collect(Collectors.toList()); List<Integer> flattenedList = collectedList.stream() .flatMap(Collection::stream) .collect(Collectors.toList()); return flattenedList; } private static ArrayList<Integer> computeLogic(int maxValue, Wrapper wrapper, Integer fixMeCurrentNumber) { if((wrapper.getSavedNumbers() == null && fixMeCurrentNumber <= maxValue) || (wrapper.getSavedNumbers().size() == 0 && fixMeCurrentNumber <= maxValue )){ ArrayList<Integer> fixMeList = new ArrayList<>(); fixMeList.add(fixMeCurrentNumber); wrapper.setSavedNumbers(fixMeList); wrapper.setCurrentTotalOfList(fixMeCurrentNumber); } else { ArrayList<Integer> copyToBeItereatedOver = new ArrayList<>(); copyToBeItereatedOver = wrapper.getSavedNumbers(); copyToBeItereatedOver.stream().forEach(savedNumber -> { validateSavedNumbers(maxValue, wrapper, fixMeCurrentNumber); }); } return wrapper.getSavedNumbers(); } private static void validateSavedNumbers(int maxValue, Wrapper wrapper, Integer fixMeCurrentNumber) { if(wrapper.getCurrentTotalOfList() < maxValue && wrapper.getCurrentTotalOfList() + fixMeCurrentNumber <= maxValue) { ArrayList<Integer> savedNumbers = new ArrayList<>(); savedNumbers = wrapper.getSavedNumbers(); //tried passing a wrapper and still no luck. savedNumbers.add(fixMeCurrentNumber); //wrapper.setSavedNumbers(savedNumbers); //wrapper.setCurrentTotalOfList(wrapper.getCurrentTotalOfList() + fixMeCurrentNumber); } } }
Wrapper.java
package models; import lombok.Data; import java.util.ArrayList; @Data public class Wrapper { ArrayList<Integer> savedNumbers; int currentTotalOfList; }
解决方案
1. ConcurrentModificationException 修复方案
异常核心原因:遍历copyToBeItereatedOver.stream()的同时直接修改了同一个ArrayList(wrapper.getSavedNumbers()),Stream的forEach遍历依赖迭代器,此时修改集合会触发快速失败机制。
修复思路:避免遍历原集合时修改它,改用创建新集合保存中间结果:
- 在
validateSavedNumbers中,创建原集合的副本添加元素后,再更新wrapper的引用; - 在
computeLogic的else分支中,先获取原集合的快照,遍历快照的同时修改新集合。
修改后的关键代码片段:
private static void validateSavedNumbers(int maxValue, Wrapper wrapper, Integer fixMeCurrentNumber) { if(wrapper.getCurrentTotalOfList() < maxValue && wrapper.getCurrentTotalOfList() + fixMeCurrentNumber <= maxValue) { // 创建原集合副本,避免遍历原集合时修改引发异常 ArrayList<Integer> newSavedNumbers = new ArrayList<>(wrapper.getSavedNumbers()); newSavedNumbers.add(fixMeCurrentNumber); wrapper.setSavedNumbers(newSavedNumbers); wrapper.setCurrentTotalOfList(wrapper.getCurrentTotalOfList() + fixMeCurrentNumber); } }
同时调整computeLogic的else分支:
else { // 获取当前集合的快照,避免遍历过程中集合被修改导致异常 ArrayList<Integer> snapshot = new ArrayList<>(wrapper.getSavedNumbers()); snapshot.forEach(savedNumber -> { validateSavedNumbers(maxValue, wrapper, fixMeCurrentNumber); }); }
2. 更简洁的Stream实现思路
现有逻辑绕且易出错,推荐直接用Stream生成所有合法三元组,再过滤和为目标值的组合:
import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; public class ThreeSumStream { public static void main(String[] args) { int target = 13; List<Integer> nums = List.of(6,4,7,9,3,12); List<List<Integer>> result = findThreeSum(nums, target); System.out.println("answer: " + result); } public static List<List<Integer>> findThreeSum(List<Integer> nums, int target) { return IntStream.range(0, nums.size()) // 第一个数的索引i .flatMap(i -> IntStream.range(i + 1, nums.size()) // 第二个数的索引j,j > i避免重复组合 .flatMap(j -> IntStream.range(j + 1, nums.size()) // 第三个数的索引k,k > j .filter(k -> nums.get(i) + nums.get(j) + nums.get(k) == target) .mapToObj(k -> List.of(nums.get(i), nums.get(j), nums.get(k))) ) ) .collect(Collectors.toList()); } }
该实现优势:
- 完全基于Stream API,无常规循环;
- 通过
i < j < k的索引范围避免生成重复三元组; - 逻辑清晰,直接过滤符合条件的组合;
- 无并发修改风险,因为未在遍历过程中修改集合。
若需扁平化结果(合并所有三元组元素为一个列表),可在最后添加.flatMap(List::stream).collect(Collectors.toList())。
内容的提问来源于stack exchange,提问作者David Westenberger
相关产品推荐
相关产品推荐

