You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java19+Vavr实战:如何将Either<Error,List<User>>转为Seq<Either<Error,User>>?

环境
  • Java 19
  • Vavr(最新版)
需求与问题

我希望构建一个XML文件处理流程,支持拆分子处理步骤,每个步骤可能执行失败,因此使用Vavr的Either来控制流程走向。流程步骤如下:

  1. 从云存储加载XML文件
  2. 解析XML → List
  3. List → List
  4. List → List
  5. 将每个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 07:05:17