Java19+Vavr实战:如何将Either<Error,List<User>>转为Seq<Either<Error,User>>?
环境
- Java 19
- Vavr(最新版)
需求与问题
我希望构建一个XML文件处理流程,支持拆分子处理步骤,每个步骤可能执行失败,因此使用Vavr的Either来控制流程走向。流程步骤如下:
- 从云存储加载XML文件
- 解析XML → List
- List
→ List - List
→ List - 将每个File存储至云存储(网络操作)
额外要求:
- 每个处理步骤需处理失败场景
- 将单个文件的处理流程拆分为多个User对象的处理
- 使用Either确保流程全成功或失败
模拟代码
public class Main { private Cloud cloud; public static void main(String[] args) { Main main = new Main(); main.retrieve("path/filename.xml") .flatMap(file -> main.parse(file)) .flatMap(people -> main.toUsers(people)) // 我想把内部的Either拆成Seq,逐个处理每个User // 转换成文件并分别存储 .flatMap(users -> main.store(users)) // 如果有一个文件存储操作失败,我希望停止并重试该操作 .peekLeft(error -> System.out.println(error.formatted())); } /** * 将每个User存储到云存储,单个文件可能失败 * * @param users * @return io.vavr.control.Either */ private Either<Error, String> store(List<User> users) { return Try.of( () -> { for (User user: users) { store(user); } return "success"; }) .toEither() .mapLeft(throwable -> new FileStoreError(throwable.getMessage())); } /** * 网络文件存储,使用云API存储文件 * * @param user * @throws RuntimeException */ private void store(User user) throws RuntimeException { cloud.store(user); } private Either<Error, List<User>> toUsers(List<Person> people) { return Try.of(() -> people.map(person -> new User(person.name())).toList()) .toEither() .mapLeft(throwable -> new ParseError(throwable.getMessage())); } /** * 从文件中读取Person列表 * * @param file * @return io.vavr.control.Either */ private Either<Error, List<Person>> parse(File file) { return Try.of(() -> people(file)) .toEither() .mapLeft(throwable -> new ParseError(throwable.getMessage())); } /** * 从文件加载Person对象列表 * * <p>可能失败! * * @param file 包含人员数据的输入文件 * @return {@link Person}列表 * @throws Exception 文件操作可能失败 */ private List<Person> people(File file) throws Exception { return List.of(new Person("A"), new Person("B")); } /** * 从云存储加载文件 * * @param name * @return io.vavr.control.Either */ public Either<Error, File> retrieve(String name) { return Try.of(() -> loadFile(name)) .toEither() .mapLeft(throwable -> new FileLoadingError(throwable.getMessage())); } /** * 从网络加载文件 * * @param name 要加载的文件名 * @return 文件 * @throws Exception 可能失败 */ private File loadFile(String name) throws Exception { return new File(name); } }
核心问题
如何将Either<Error, List<User>>转换为Seq类型的Either<Error, User>,以便逐个处理每个User对象?
解决方案
1. 转换Either<Error, List<User>>为Seq<Either<Error, User>>
利用Vavr的fold方法可以简洁处理成功/失败两种场景:
Either<Error, List<User>> usersEither = ...; Seq<Either<Error, User>> userEithers = usersEither.fold( // 如果原Either是失败状态,生成包含该错误的单元素Seq error -> Seq.of(Either.left(error)), // 如果是成功状态,把每个User包装成Right类型的Either组成Seq users -> users.map(Either::right) );
这样不管原Either是成功还是失败,都能得到符合要求的Seq<Either<Error, User>>。
2. 逐个处理User并实现失败即停+重试
首先修改存储方法,让它处理单个User并返回Either<Error, User>:
private Either<Error, User> storeSingle(User user) { return Try.of(() -> { cloud.store(user); return user; }) .toEither() .mapLeft(throwable -> new FileStoreError(throwable.getMessage())); }
然后在主流程中结合foldLeft实现逐个处理、失败即停,同时用Try.retry添加重试逻辑:
main.retrieve("path/filename.xml") .flatMap(file -> main.parse(file)) .flatMap(people -> main.toUsers(people)) // 拆分为单个User的Either序列 .fold( error -> Seq.of(Either.left(error)), users -> users.map(Either::right) ) // 逐个处理,失败则终止流程 .foldLeft(Either.right(Seq.<User>empty()), (acc, userEither) -> { if (acc.isLeft()) { return acc; // 已有失败记录,直接返回错误 } // 对单个存储操作重试3次,失败后返回错误 return Try.retry(3, () -> storeSingle(userEither.get())) .toEither() .map(savedUser -> acc.get().append(savedUser)); }) .peekLeft(error -> System.out.println(error.formatted())) .peek(savedUsers -> System.out.println("成功存储用户数:" + savedUsers.size()));
3. 确保流程全成功或失败
上述foldLeft逻辑天然满足"全成功才返回成功"的要求:只要有一个User存储失败,整个流程就会返回Left(error);只有所有User都存储成功,才会返回Right(已存储User列表)。
内容的提问来源于stack exchange,提问作者Gadi
相关产品推荐
相关产品推荐

