如何简洁处理Validation流Reduce操作中单个状态抛异常的问题
嘿,这个问题我太熟了!Vavr的Validation本身就为这种「批量验证+收集所有错误」的场景提供了现成的API,完全不用自己写reduce去硬扛那四种分支情况,还能完美避开你遇到的两个坑——有效状态调用getError()抛异常、多余分号。
先拆解下你原来的问题根源:
- 有效状态调用
getError()抛异常:Validation的设计就是「Valid状态下没有错误值」,所以getError()只在Invalid状态下安全,硬在Valid状态调用肯定炸; - 多余分号:你不管当前Validation是否有效都直接拼接错误字符串,导致空字符串和有效状态的无错误值被强行拼接出多余符号。
最优解决方案:用Vavr原生的合并API
Validation本质是Applicative Functor(不是Monad,Monad是顺序短路验证,而Applicative是并行收集所有错误,刚好匹配你的需求),所以直接用它的原生合并方法就行:
情况1:固定数量的Validation(比如你例子里的a、b、c三个)
用Validation.combine(),专门用来合并固定数量的Validation,自动收集所有错误:
// 合并三个Validation,收集所有错误并转成分号分隔的字符串 Validation<String, Foo> result = Validation.combine(a, b, c) // 所有验证通过时,返回你需要的foo(这里不需要用到三个Foo的具体值,直接返回即可) .ap((fooA, fooB, fooC) -> foo) // 把收集到的错误序列转成分号分隔的字符串,自动过滤空错误 .mapErrors(errors -> String.join("; ", errors));
情况2:数量不固定的Validation(比如从Stream/集合里来)
用Validation.sequence()把多个Validation转成一个「错误为序列、结果为序列」的Validation,再处理成你要的格式:
// 先把Stream转成Vavr的Seq(也可以用普通集合,sequence支持Iterable) Seq<Validation<String, Foo>> validations = Stream.of(a, b, c) .collect(Collectors.toCollection(ArrayDeque::new)); // 批量合并,收集所有错误 Validation<Seq<String>, Seq<Foo>> combined = Validation.sequence(validations); // 转成你需要的最终格式:Valid时返回foo,Invalid时返回分号分隔的错误串 Validation<String, Foo> result = combined .map(fooSeq -> foo) // 不管有多少个有效Foo,都返回目标foo .mapErrors(errors -> String.join("; ", errors));
为什么这两种方法能解决问题?
- 完全不用显式处理四种分支:Vavr的API已经帮你封装了「全有效/部分无效/全无效」的所有情况;
- 不会触发
getError()异常:只有当Validation处于Invalid状态时,才会处理错误值,Valid状态下根本不会碰错误相关的逻辑; - 没有多余分号:
String.join()只会拼接非空的错误字符串(因为只有Invalid的Validation才会贡献错误值到序列里)。
如果你非要用Stream.reduce(不推荐,但可以实现)
如果因为某些限制必须用Stream的reduce,也可以借助Validation.zip()和fold()来安全处理,避免直接调用getError():
Validation<String, Foo> result = Stream.of(a, b, c) .reduce(Validation.valid(foo), (acc, current) -> acc.zip(current) .map(tuple -> foo) // 合并后有效时返回目标foo // 合并错误:只有当两个都无效时才拼接,单个无效时直接用对应错误 .mapErrors(errors -> { if (errors._1 != null && errors._2 != null) { return String.join("; ", errors._1, errors._2); } return errors._1 != null ? errors._1 : errors._2; }) );
不过这种写法还是不如原生的combine/sequence简洁,毕竟原生API就是为这种场景设计的。
内容的提问来源于stack exchange,提问作者Andrew Cheong
相关产品推荐
相关产品推荐

