如何用Java Stream将一种类型集合归约为另一种类型的单个对象
用Java Stream的reduce实现类型归约
我来帮你搞定这个Stream归约的问题!首先,你原来用forEach修改外部对象的方式虽然能跑,但确实不符合函数式编程的风格,用reduce来实现是更优雅的选择,不过得注意正确的用法,尤其是避免副作用和线程安全问题。
推荐的纯函数式实现(无副作用,支持并行流)
@Test public void streams() { List<MyData> collection = Arrays.asList( new MyData("1", "cool"), new MyData("2", "green"), new MyData("3", "night") ); MyResult result = collection.stream() .reduce(new MyResult(), // 初始值:创建一个空的MyResult (currentRes, data) -> { // 找到目标元素时,返回一个新的MyResult对象,避免修改原对象 if ("2".equals(data.key)) { MyResult updatedRes = new MyResult(); updatedRes.color = data.value; // 如果MyResult还有其他字段,记得复制currentRes里的原有值 // updatedRes.otherField = currentRes.otherField; return updatedRes; } // 没找到的话,直接返回当前结果 return currentRes; }, // 并行流时的组合器:合并两个结果,这里我们取有color值的那个 (res1, res2) -> res2.color != null ? res2 : res1); // 验证结果 MyResult expectedResult = new MyResult(); expectedResult.color = "green"; assertEquals(expectedResult.color, result.color); }
为什么要返回新对象?
函数式编程里推荐避免修改传入的参数(也就是currentRes),因为如果使用并行流的话,多个线程会同时操作同一个对象,导致线程安全问题。返回新对象能保证每个累加步骤都是独立的,并行流也能安全运行。
简化版(串行流可用,不推荐并行)
如果你的场景肯定是串行流,也可以直接修改传入的结果对象,但这种方式有副作用,不推荐在并行流中使用:
MyResult result = collection.stream() .reduce(new MyResult(), (res, data) -> { if ("2".equals(data.key)) { res.color = data.value; } return res; }, (res1, res2) -> { // 并行时合并结果,这里取第二个结果的color(如果有的话) if (res2.color != null) { res1.color = res2.color; } return res1; });
更语义化的替代方案
其实你的需求更偏向于查找单个元素并转换,用filter+findFirst+map会比reduce更直观,语义也更清晰:
MyResult result = collection.stream() .filter(data -> "2".equals(data.key)) // 过滤出key为"2"的元素 .findFirst() // 获取第一个匹配的元素(你的例子里只有一个) .map(data -> { // 转换为MyResult对象 MyResult res = new MyResult(); res.color = data.value; return res; }) .orElse(new MyResult()); // 如果没找到匹配元素,返回默认的MyResult
这种方式代码更易读,也符合你的实际需求场景——毕竟reduce更多用于聚合多个元素的信息到一个结果中,而不是查找单个元素。
内容的提问来源于stack exchange,提问作者sschmeck
相关产品推荐
相关产品推荐

